Basic framework for vacuum arc furnace multiblock. Moved multiblock methods to MultiblockHelper.

This commit is contained in:
XevianLight
2026-01-18 21:06:14 -07:00
parent 557c2761a7
commit ea998165be
32 changed files with 1335 additions and 245 deletions

View File

@@ -1,6 +1,7 @@
// 1.21.1 2026-01-17T20:45:45.6230298 Loot Tables
// 1.21.1 2026-01-18T19:51:57.3558369 Loot Tables
69d8318ddba171526d1fabb87d9d93548ed8598e data/aphelion/loot_table/blocks/arc_furnace_casing.json
05f08985e601d30116f67e2f07b48b03b40cdca6 data/aphelion/loot_table/blocks/block_steel.json
ff43a9c3741faf10b1e156a7a74d5cfb035cc118 data/aphelion/loot_table/blocks/dimension_changer.json
b63130d9c10485676303d729807b6fcaac080294 data/aphelion/loot_table/blocks/electric_arc_furnace.json
1ab50c99e9f478840b9d003fd56ebdcab12fbbce data/aphelion/loot_table/blocks/test_block.json
7d8eeb99a1bc942a6e2cf292b21fd4534062b5ab data/aphelion/loot_table/blocks/vacuum_arc_furnace_controller.json

View File

@@ -1,12 +1,15 @@
// 1.21.1 2026-01-17T21:22:18.4335623 Block States: aphelion
// 1.21.1 2026-01-18T19:51:57.3548439 Block States: aphelion
851ff42f7b21dec86107c8e0cefb3934ae4ebc08 assets/aphelion/blockstates/block_steel.json
30b9c0efd7aaadb5412d98e4568f98b3632adbb9 assets/aphelion/blockstates/dimension_changer.json
cb4287104006c80c8396b290ab5258df65d62cef assets/aphelion/blockstates/electric_arc_furnace.json
b86c50fddcf6c8c6c19cb748529239d5962a3ede assets/aphelion/blockstates/test_block.json
a810b97f4dace35d026f28d96cb9c47c93600d75 assets/aphelion/models/block/block_steel.json
2d3592b7ab7132908709243e97540151e0fb762e assets/aphelion/models/block/dimension_changer.json
5f7e8674070f31a63875b5d6147153bfa0eef61a assets/aphelion/models/block/electric_arc_furnace.json
e0971228b4a1c4bc9dbab58a7dacdc3ae6037e02 assets/aphelion/models/block/test_block.json
cdc831b0f1c462be64825fd34bd446e5b95afac6 assets/aphelion/models/item/arc_furnace_casing.json
3599f9037eb2f66de1765318b97ab564c3eae92f assets/aphelion/models/item/block_steel.json
db0ec473a016ce05c258cde18a217d47a9ea8324 assets/aphelion/models/item/dimension_changer.json
279080c06ada87f54fd0a7b885b256dbe25a946a assets/aphelion/models/item/electric_arc_furnace.json
74418ef1cf678e72e7534924274688ef5a68af0e assets/aphelion/models/item/test_block.json
88ca3602517e99f7feaed57eddfc96965a25761c assets/aphelion/models/item/vacuum_arc_furnace_controller.json

View File

@@ -15,6 +15,7 @@ import net.xevianlight.aphelion.recipe.ModRecipes;
import net.xevianlight.aphelion.screen.ElectricArcFurnaceScreen;
import net.xevianlight.aphelion.screen.ModMenuTypes;
import net.xevianlight.aphelion.screen.TestBlockScreen;
import net.xevianlight.aphelion.screen.VacuumArcFurnaceScreen;
import org.slf4j.Logger;
import com.mojang.logging.LogUtils;
@@ -120,6 +121,7 @@ public class Aphelion {
public static void registerScreens(RegisterMenuScreensEvent event) {
event.register(ModMenuTypes.TEST_BLOCK_MENU.get(), TestBlockScreen::new);
event.register(ModMenuTypes.ELECTRIC_ARC_FURNACE_MENU.get(), ElectricArcFurnaceScreen::new);
event.register(ModMenuTypes.VACUUM_ARC_FURNACE_MENU.get(), VacuumArcFurnaceScreen::new);
}
}
}

View File

@@ -2,6 +2,11 @@ package net.xevianlight.aphelion.block.custom;
import com.mojang.serialization.MapCodec;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.SimpleMenuProvider;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
@@ -13,13 +18,17 @@ import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.phys.BlockHitResult;
import net.xevianlight.aphelion.block.entity.custom.EAFPartEntity;
import net.xevianlight.aphelion.block.entity.custom.ElectricArcFurnaceEntity;
import net.xevianlight.aphelion.block.entity.custom.VacuumArcFurnaceControllerEntity;
import net.xevianlight.aphelion.util.AphelionBlockStateProperties;
import net.xevianlight.aphelion.util.MultiblockHelper;
import org.jetbrains.annotations.Nullable;
public class ArcFurnaceCasingBlock extends BaseEntityBlock {
public static final BooleanProperty FORMED = BooleanProperty.create("formed");
public static final BooleanProperty FORMED = AphelionBlockStateProperties.FORMED;
public ArcFurnaceCasingBlock(Properties properties) {
super(properties);
@@ -27,6 +36,11 @@ public class ArcFurnaceCasingBlock extends BaseEntityBlock {
.setValue(FORMED, false));
}
@Override
protected MapCodec<? extends BaseEntityBlock> codec() {
return CODEC;
}
public static Properties getProperties() {
return Properties
.of()
@@ -42,18 +56,18 @@ public class ArcFurnaceCasingBlock extends BaseEntityBlock {
public static final MapCodec<ArcFurnaceCasingBlock> CODEC = simpleCodec(ArcFurnaceCasingBlock::new);
@Override
protected MapCodec<? extends BaseEntityBlock> codec() {
return CODEC;
}
// @Override
// protected MapCodec<? extends BaseEntityBlock> codec() {
// return CODEC;
// }
@Override
public @Nullable BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) {
return new EAFPartEntity(blockPos, blockState);
}
// @Override
// public @Nullable BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) {
// return new EAFPartEntity(blockPos, blockState);
// }
private void pingNearbyController(Level level, BlockPos pos) {
int r = 3;
int r = 5;
BlockPos.MutableBlockPos mp = new BlockPos.MutableBlockPos();
for (int dx=-r; dx<=r; dx++)
@@ -61,14 +75,28 @@ public class ArcFurnaceCasingBlock extends BaseEntityBlock {
for (int dz=-r; dz<=r; dz++) {
mp.set(pos.getX()+dx, pos.getY()+dy, pos.getZ()+dz);
BlockEntity be = level.getBlockEntity(mp);
if (be instanceof ElectricArcFurnaceEntity eaf) {
if (level.getBlockState(eaf.getBlockPos()).getBlock() instanceof ElectricArcFurnace) {
eaf.tryForm();
if (be instanceof VacuumArcFurnaceControllerEntity vaf) {
if (level.getBlockState(vaf.getBlockPos()).getBlock() instanceof VacuumArcFurnaceController) {
MultiblockHelper.tryForm(level, vaf.getBlockState(), vaf.getBlockPos(), VacuumArcFurnaceControllerEntity.SHAPE, AphelionBlockStateProperties.FORMED);
}
}
}
}
@Override
public InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult result) {
if (state.getValue(AphelionBlockStateProperties.FORMED)) {
if (!level.isClientSide && player instanceof ServerPlayer serverPlayer && level.getBlockEntity(pos) instanceof EAFPartEntity eafPartEntity) {
if (eafPartEntity.getControllerPos() != null)
if (level.getBlockEntity(eafPartEntity.getControllerPos()) instanceof VacuumArcFurnaceControllerEntity)
serverPlayer.openMenu(new SimpleMenuProvider((VacuumArcFurnaceControllerEntity) level.getBlockEntity(eafPartEntity.getControllerPos()), Component.literal("Vacuum Arc Furnace")), eafPartEntity.getControllerPos());
}
return InteractionResult.sidedSuccess(level.isClientSide);
}
return InteractionResult.FAIL;
}
@Override
public void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean movedByPiston) {
super.onPlace(state, level, pos, oldState, movedByPiston);
@@ -97,4 +125,9 @@ public class ArcFurnaceCasingBlock extends BaseEntityBlock {
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(FORMED);
}
@Override
public @Nullable BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) {
return new EAFPartEntity(blockPos, blockState);
}
}

View File

@@ -27,6 +27,8 @@ import net.minecraft.world.phys.BlockHitResult;
import net.neoforged.neoforge.items.ItemStackHandler;
import net.xevianlight.aphelion.block.entity.custom.ElectricArcFurnaceEntity;
import net.xevianlight.aphelion.core.init.ModBlockEntities;
import net.xevianlight.aphelion.util.AphelionBlockStateProperties;
import net.xevianlight.aphelion.util.MultiblockHelper;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
@@ -36,7 +38,7 @@ public class ElectricArcFurnace extends BaseEntityBlock {
public static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING;
public static final BooleanProperty LIT = BlockStateProperties.LIT;
public static final BooleanProperty FORMED = BooleanProperty.create("formed");
public static final BooleanProperty FORMED = AphelionBlockStateProperties.FORMED;
public ElectricArcFurnace(Properties properties) {
super(properties);
@@ -72,8 +74,8 @@ public class ElectricArcFurnace extends BaseEntityBlock {
@Override
public InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult result) {
if (!level.isClientSide && player instanceof ServerPlayer serverPlayer && level.getBlockEntity(pos) instanceof ElectricArcFurnaceEntity electricArcFurnaceEntity && state.getValue(FORMED)) {
serverPlayer.openMenu(new SimpleMenuProvider(electricArcFurnaceEntity, Component.literal("Electric Arc Furnace")), pos);
if (!level.isClientSide && player instanceof ServerPlayer serverPlayer && level.getBlockEntity(pos) instanceof ElectricArcFurnaceEntity electricArcFurnaceEntity) {
serverPlayer.openMenu(new SimpleMenuProvider(electricArcFurnaceEntity, Component.literal("Electric Arc Furnace")), pos);
}
return InteractionResult.sidedSuccess(level.isClientSide);
}
@@ -95,7 +97,7 @@ public class ElectricArcFurnace extends BaseEntityBlock {
if (!level.isClientSide && state.getBlock() != newState.getBlock()) {
BlockEntity blockEntity= level.getBlockEntity(pos);
if (blockEntity instanceof ElectricArcFurnaceEntity electricArcFurnaceEntity) {
if(state.getValue(FORMED)) electricArcFurnaceEntity.unformForRemoval();
// if(state.getValue(FORMED)) MultiblockHelper.unformForRemoval(level, state, pos, ElectricArcFurnaceEntity.SHAPE, AphelionBlockStateProperties.FORMED);
electricArcFurnaceEntity.drops();
}
}
@@ -107,9 +109,9 @@ public class ElectricArcFurnace extends BaseEntityBlock {
super.onPlace(state, level, pos, oldState, movedByPiston);
if (!level.isClientSide() && oldState.getBlock() != state.getBlock()) {
BlockEntity blockEntity= level.getBlockEntity(pos);
if (blockEntity instanceof ElectricArcFurnaceEntity electricArcFurnaceEntity) {
electricArcFurnaceEntity.tryForm();
}
// if (blockEntity instanceof ElectricArcFurnaceEntity electricArcFurnaceEntity) {
// MultiblockHelper.tryForm(level, state, pos, ElectricArcFurnaceEntity.SHAPE, AphelionBlockStateProperties.FORMED);
// }
}
}

View File

@@ -0,0 +1,196 @@
package net.xevianlight.aphelion.block.custom;
import com.mojang.serialization.MapCodec;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.SimpleMenuProvider;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import net.minecraft.world.phys.BlockHitResult;
import net.neoforged.neoforge.items.ItemStackHandler;
import net.xevianlight.aphelion.block.entity.custom.VacuumArcFurnaceControllerEntity;
import net.xevianlight.aphelion.core.init.ModBlockEntities;
import net.xevianlight.aphelion.util.AphelionBlockStateProperties;
import net.xevianlight.aphelion.util.MultiblockHelper;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
public class VacuumArcFurnaceController extends BaseEntityBlock {
public static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING;
public static final BooleanProperty LIT = BlockStateProperties.LIT;
public static final BooleanProperty FORMED = AphelionBlockStateProperties.FORMED;
public VacuumArcFurnaceController(Properties properties) {
super(properties);
this.registerDefaultState(this.getStateDefinition().any()
.setValue(FORMED, false));
}
public static final MapCodec<VacuumArcFurnaceController> CODEC = simpleCodec(VacuumArcFurnaceController::new);
private final int INPUT_SLOT = 0;
private final int SECONDARY_INPUT_SLOT = 1;
private final int OUTPUT_SLOT = 2;
private final int ENERGY_SLOT = 3;
@Override
protected MapCodec<? extends BaseEntityBlock> codec() {
return CODEC;
}
public static Properties getProperties() {
return Properties
.of()
.sound(SoundType.METAL)
.destroyTime(2f)
.explosionResistance(10f)
.requiresCorrectToolForDrops();
}
public static Item.Properties getItemProperties() {
return new Item.Properties();
}
@Override
public InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult result) {
if (!level.isClientSide && player instanceof ServerPlayer serverPlayer && level.getBlockEntity(pos) instanceof VacuumArcFurnaceControllerEntity vacuumArcFurnaceEntity) {
if (vacuumArcFurnaceEntity.isFormed())
serverPlayer.openMenu(new SimpleMenuProvider(vacuumArcFurnaceEntity, Component.literal("Vacuum Arc Furnace")), pos);
}
return InteractionResult.sidedSuccess(level.isClientSide);
}
@Override
public @Nullable BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) {
return new VacuumArcFurnaceControllerEntity(blockPos, blockState);
}
@Override
public RenderShape getRenderShape(BlockState pState) {
return RenderShape.MODEL;
}
@Override
protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean movedByPiston) {
if (!level.isClientSide && state.getBlock() != newState.getBlock()) {
BlockEntity blockEntity= level.getBlockEntity(pos);
if (blockEntity instanceof VacuumArcFurnaceControllerEntity vacuumArcFurnaceEntity) {
if(state.getValue(FORMED)) MultiblockHelper.unformForRemoval(level, state, pos, vacuumArcFurnaceEntity.SHAPE, AphelionBlockStateProperties.FORMED);
vacuumArcFurnaceEntity.drops();
}
}
super.onRemove(state, level, pos, newState, movedByPiston);
}
@Override
protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean movedByPiston) {
super.onPlace(state, level, pos, oldState, movedByPiston);
if (!level.isClientSide() && oldState.getBlock() != state.getBlock()) {
BlockEntity blockEntity= level.getBlockEntity(pos);
if (blockEntity instanceof VacuumArcFurnaceControllerEntity vacuumArcFurnaceEntity) {
MultiblockHelper.tryForm(level, state, pos, vacuumArcFurnaceEntity.SHAPE, AphelionBlockStateProperties.FORMED);
}
}
}
public static int getRedstoneSignalFromItemHandler(@javax.annotation.Nullable ItemStackHandler itemStackHandler, List<Integer> slots) {
if (itemStackHandler == null) {
return 0;
} else {
int i = 0;
float f = 0.0F;
for(int slot : slots) {
ItemStack itemstack = itemStackHandler.getStackInSlot(slot);
if (!itemstack.isEmpty()) {
f += (float)itemstack.getCount() / (float)Math.min(itemStackHandler.getSlotLimit(slot), itemstack.getMaxStackSize());
++i;
}
}
f /= (float)slots.size();
return Mth.floor(f * 14.0F) + (i > 0 ? 1 : 0);
}
}
@Override
protected boolean isSignalSource(BlockState state) {
return true;
}
@Override
protected int getSignal(BlockState state, BlockGetter level, BlockPos pos, Direction direction) {
return super.getSignal(state, level, pos, direction);
}
@Override
protected boolean hasAnalogOutputSignal(BlockState state) {
return true;
}
@Override
protected int getAnalogOutputSignal(BlockState blockState, Level level, BlockPos pos) {
List<Integer> slots = new ArrayList<>();
slots.add(VacuumArcFurnaceControllerEntity.INPUT_SLOT);
slots.add(VacuumArcFurnaceControllerEntity.SECONDARY_INPUT_SLOT);
slots.add(VacuumArcFurnaceControllerEntity.OUTPUT_SLOT);
VacuumArcFurnaceControllerEntity vacuumArcFurnaceEntity = ((VacuumArcFurnaceControllerEntity) level.getBlockEntity(pos));
if (vacuumArcFurnaceEntity != null)
return getRedstoneSignalFromItemHandler(vacuumArcFurnaceEntity.inventory, slots);
return 0;
}
@Override
public @Nullable <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> blockEntityType) {
if (level.isClientSide) {
return null;
}
return createTickerHelper(blockEntityType, ModBlockEntities.VACUUM_ARC_FURNACE_ENTITY.get(), (level1, blockPos, blockState, vacuumArcFurnaceEntity) -> vacuumArcFurnaceEntity.tick(level1, blockPos, blockState));
}
@Override
protected BlockState rotate(BlockState state, Rotation rotation) {
return state.setValue(FACING, rotation.rotate(state.getValue(FACING)));
}
@Override
protected BlockState mirror(BlockState state, Mirror mirror) {
return state.rotate(mirror.getRotation(state.getValue(FACING)));
}
@Override
public @Nullable BlockState getStateForPlacement(BlockPlaceContext context) {
return this.defaultBlockState().setValue(FACING, context.getHorizontalDirection().getOpposite()).setValue(LIT, false).setValue(FORMED, false);
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(FACING, LIT, FORMED);
}
}

View File

@@ -7,9 +7,10 @@ import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.xevianlight.aphelion.core.init.ModBlockEntities;
import net.xevianlight.aphelion.util.IMultiblockPart;
import org.jetbrains.annotations.Nullable;
public class EAFPartEntity extends BlockEntity {
public class EAFPartEntity extends BlockEntity implements IMultiblockPart {
@Nullable private BlockPos controllerPos;
public EAFPartEntity(BlockPos pos, BlockState blockState) {

View File

@@ -8,12 +8,9 @@ import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientGamePacketListener;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.Containers;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.MobSpawnType;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
@@ -23,26 +20,24 @@ import net.minecraft.world.item.crafting.*;
import net.minecraft.world.level.Level;
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.Vec3;
import net.neoforged.neoforge.capabilities.Capabilities;
import net.neoforged.neoforge.energy.IEnergyStorage;
import net.neoforged.neoforge.items.IItemHandler;
import net.neoforged.neoforge.items.ItemStackHandler;
import net.xevianlight.aphelion.block.custom.ArcFurnaceCasingBlock;
import net.xevianlight.aphelion.block.custom.ElectricArcFurnace;
import net.xevianlight.aphelion.block.entity.energy.ModEnergyStorage;
import net.xevianlight.aphelion.core.init.ModBlockEntities;
import net.xevianlight.aphelion.core.init.ModBlocks;
import net.xevianlight.aphelion.recipe.ElectricArcFurnaceRecipe;
import net.xevianlight.aphelion.recipe.ElectricArcFurnaceRecipeInput;
import net.xevianlight.aphelion.recipe.ModRecipes;
import net.xevianlight.aphelion.screen.ElectricArcFurnaceMenu;
import net.xevianlight.aphelion.util.AphelionBlockStateProperties;
import net.xevianlight.aphelion.util.MultiblockHelper;
import net.xevianlight.aphelion.util.SidedSlotHandler;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.*;
public class ElectricArcFurnaceEntity extends BlockEntity implements MenuProvider {
@@ -108,7 +103,7 @@ public class ElectricArcFurnaceEntity extends BlockEntity implements MenuProvide
public void tick(Level level, BlockPos pos, BlockState blockState) {
if (!blockState.getValue(ElectricArcFurnace.FORMED))
if (!blockState.getValue(AphelionBlockStateProperties.FORMED))
return;
chargeFromItem();
@@ -316,6 +311,7 @@ public class ElectricArcFurnaceEntity extends BlockEntity implements MenuProvide
}
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);
private final IItemHandler outputHandler = new SidedSlotHandler(inventory, new int[]{2,3}, false, true);
private final IItemHandler jadeHandler = new SidedSlotHandler(inventory, new int[]{0}, false, false);
@@ -325,13 +321,18 @@ public class ElectricArcFurnaceEntity extends BlockEntity implements MenuProvide
}
public IEnergyStorage getEnergyStorage(@Nullable Direction direction) {
return ENERGY_STORAGE;
}
public IEnergyStorage getTrueEnergyStorage(@Nullable Direction direction) {
return this.ENERGY_STORAGE;
}
private final ModEnergyStorage ENERGY_STORAGE = createEnergyStorage();
private final ModEnergyStorage ENERGY_STORAGE = createEnergyStorage(ENERGY_CAPACITY, MAX_TRANSFER);
private final ModEnergyStorage NULL_ENERGY_STORAGE = createEnergyStorage(0, 0);
private ModEnergyStorage createEnergyStorage() {
return new ModEnergyStorage(ENERGY_CAPACITY, MAX_TRANSFER) {
private ModEnergyStorage createEnergyStorage(int capacity, int transfer) {
return new ModEnergyStorage(capacity, transfer) {
@Override
public void onEnergyChanged() {
setChanged();
@@ -378,7 +379,7 @@ public class ElectricArcFurnaceEntity extends BlockEntity implements MenuProvide
@Override
public @Nullable AbstractContainerMenu createMenu(int i, Inventory inventory, Player player) {
return new ElectricArcFurnaceMenu(i, inventory, this, this.data);
return new ElectricArcFurnaceMenu(i, inventory, this, this.data);
}
@Nullable
@@ -403,132 +404,8 @@ public class ElectricArcFurnaceEntity extends BlockEntity implements MenuProvide
};
}
/// MULTIBLOCK
/// LOGIC
/// BELOW
private boolean formed = false; // cached runtime state
private static final BlockPos[] SHAPE = new BlockPos[] {
new BlockPos(1, 0, 0),
new BlockPos(0, 0, 1),
new BlockPos(1, 0, 1),
new BlockPos(0, 1, 0),
new BlockPos(1, 1, 0),
new BlockPos(0, 1, 1),
new BlockPos(1, 1, 1),
public static final MultiblockHelper.ShapePart[] SHAPE = new MultiblockHelper.ShapePart[] {
new MultiblockHelper.ShapePart(new BlockPos(3,0,3), s -> s.is(ModBlocks.ARC_FURNACE_CASING_BLOCK)),
// new MultiblockHelper.ShapePart(new BlockPos(-1,0,0), s -> s.is(Blocks.AMETHYST_BLOCK))
};
private List<BlockPos> getPartPositions() {
Direction facing = getBlockState().getValue(BlockStateProperties.HORIZONTAL_FACING);
BlockPos origin = getBlockPos();
List<BlockPos> out = new ArrayList<>(SHAPE.length);
for (BlockPos off : SHAPE) {
BlockPos ro = rotateY(off, facing);
out.add(origin.offset(ro));
}
return out;
}
private boolean structureMatches() {
if (level == null) return false;
for (BlockPos p : getPartPositions()) {
BlockState st = level.getBlockState(p);
// Accept only your casing/part blocks
if (!(st.getBlock() instanceof ArcFurnaceCasingBlock)) {
return false;
}
}
return true;
}
public boolean isFormed() {
return formed;
}
public void tryForm() {
if (level == null || level.isClientSide()) return;
if (!(getBlockState().getBlock() instanceof ElectricArcFurnace)) return; // don't form if not actually present
boolean valid = structureMatches();
if (valid && !formed) {
formed = true;
setFormedState(true);
linkParts();
} else if (!valid && formed) {
unform();
}
}
public void unform() {
if (level == null || level.isClientSide()) return;
formed = false;
setFormedState(false);
unlinkParts();
}
public void unformForRemoval() {
if (level == null || level.isClientSide()) return;
formed = false;
unlinkParts();
}
private void setFormedState(boolean value) {
BlockState state = getBlockState();
if (state.hasProperty(ElectricArcFurnace.FORMED) && state.getValue(ElectricArcFurnace.FORMED) != value) {
level.setBlock(worldPosition, state.setValue(ElectricArcFurnace.FORMED, value), 3);
}
}
private void linkParts() {
if (level == null || level.isClientSide()) return;
for (BlockPos p : getPartPositions()) {
BlockState st = level.getBlockState(p);
if (st.getBlock() instanceof ArcFurnaceCasingBlock && st.hasProperty(ArcFurnaceCasingBlock.FORMED)) {
if (!st.getValue(ArcFurnaceCasingBlock.FORMED)) {
level.setBlock(p, st.setValue(ArcFurnaceCasingBlock.FORMED, true), 3);
}
}
BlockEntity be = level.getBlockEntity(p);
if (be instanceof EAFPartEntity part) {
part.setControllerPos(worldPosition);
part.setChanged();
}
}
}
private void unlinkParts() {
if (level == null || level.isClientSide()) return;
for (BlockPos p : getPartPositions()) {
BlockState st = level.getBlockState(p);
if (st.getBlock() instanceof ArcFurnaceCasingBlock && st.hasProperty(ArcFurnaceCasingBlock.FORMED)) {
if (st.getValue(ArcFurnaceCasingBlock.FORMED)) {
level.setBlock(p, st.setValue(ArcFurnaceCasingBlock.FORMED, false), 3);
}
}
BlockEntity be = level.getBlockEntity(p);
if (be instanceof EAFPartEntity part && worldPosition.equals(part.getControllerPos())) {
part.setControllerPos(null);
part.setChanged();
}
}
}
}

View File

@@ -0,0 +1,507 @@
package net.xevianlight.aphelion.block.entity.custom;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.HolderLookup;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientGamePacketListener;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.world.Containers;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.ContainerData;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.BlastingRecipe;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.minecraft.world.item.crafting.RecipeType;
import net.minecraft.world.item.crafting.SingleRecipeInput;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.neoforged.neoforge.capabilities.Capabilities;
import net.neoforged.neoforge.energy.IEnergyStorage;
import net.neoforged.neoforge.items.IItemHandler;
import net.neoforged.neoforge.items.ItemStackHandler;
import net.xevianlight.aphelion.block.custom.ElectricArcFurnace;
import net.xevianlight.aphelion.block.entity.energy.ModEnergyStorage;
import net.xevianlight.aphelion.core.init.ModBlockEntities;
import net.xevianlight.aphelion.core.init.ModBlocks;
import net.xevianlight.aphelion.recipe.ElectricArcFurnaceRecipe;
import net.xevianlight.aphelion.recipe.ElectricArcFurnaceRecipeInput;
import net.xevianlight.aphelion.recipe.ModRecipes;
import net.xevianlight.aphelion.screen.VacuumArcFurnaceMenu;
import net.xevianlight.aphelion.screen.ElectricArcFurnaceMenu;
import net.xevianlight.aphelion.util.AphelionBlockStateProperties;
import net.xevianlight.aphelion.util.IMultiblockController;
import net.xevianlight.aphelion.util.MultiblockHelper;
import net.xevianlight.aphelion.util.SidedSlotHandler;
import org.jetbrains.annotations.Nullable;
import java.util.Optional;
public class VacuumArcFurnaceControllerEntity extends BlockEntity implements MenuProvider, IMultiblockController {
private final int SIZE = 4;
private int ENERGY_CAPACITY = 64000;
private int MAX_TRANSFER = 320;
private int progress = 0;
private int maxProgress = 100;
private final int DEFAULT_MAX_PROGRESS = 100;
private final ContainerData data;
private int MACHINE_ENERGY_COST = 20;
public static final int INPUT_SLOT = 0;
public static final int SECONDARY_INPUT_SLOT = 1;
public static final int OUTPUT_SLOT = 2;
public static final int ENERGY_SLOT = 3;
public VacuumArcFurnaceControllerEntity(BlockPos pos, BlockState blockState) {
super(ModBlockEntities.VACUUM_ARC_FURNACE_ENTITY.get(), pos, blockState);
this.data = new ContainerData() {
@Override
public int get(int index) {
return switch (index) {
case 0 -> VacuumArcFurnaceControllerEntity.this.progress;
case 1 -> VacuumArcFurnaceControllerEntity.this.maxProgress;
default -> 0;
};
}
@Override
public void set(int index, int pValue) {
switch (index) {
case 0: VacuumArcFurnaceControllerEntity.this.progress = pValue;
case 1: VacuumArcFurnaceControllerEntity.this.maxProgress = pValue;
}
}
@Override
public int getCount() {
return 2;
}
};
}
public final ItemStackHandler inventory = new ItemStackHandler(SIZE) {
@Override
protected void onContentsChanged(int slot) {
setChanged();
if(!level.isClientSide()) {
level.sendBlockUpdated(getBlockPos(), getBlockState(), getBlockState(), 3);
}
}
// @Override
// public boolean isItemValid(int slot, ItemStack stack) {
// if (slot == ENERGY_SLOT) {
// var capability = stack.getCapability(Capabilities.EnergyStorage.ITEM);
// return capability != null;
// }
// return super.isItemValid(slot, stack);
// }
};
public void tick(Level level, BlockPos pos, BlockState blockState) {
if (!blockState.getValue(AphelionBlockStateProperties.FORMED))
return;
chargeFromItem();
if (inventory.getStackInSlot(SECONDARY_INPUT_SLOT).isEmpty()) {
// Secondary slot is empty, try furnace recipes
if (hasFurnaceRecipe(INPUT_SLOT) && hasEnoughEnergyToCraft(MACHINE_ENERGY_COST)) {
// Recipe detected! We have enough energy to process
progress++;
useEnergy();
level.setBlockAndUpdate(pos, blockState.setValue(ElectricArcFurnace.LIT, true));
setChanged(level, pos, blockState);
if (hasCraftingFinished()) {
outputBlastingResult(INPUT_SLOT, OUTPUT_SLOT);
resetProgress();
}
} else if (hasFurnaceRecipe(INPUT_SLOT) && !hasEnoughEnergyToCraft(MACHINE_ENERGY_COST)) {
// Recipe detected but we ran out of power
level.setBlockAndUpdate(pos, blockState.setValue(ElectricArcFurnace.LIT, false));
setChanged(level, pos, blockState);
progress = progress > 0 ? progress - 1 : 0;
} else {
// Invalid recipe
resetProgress();
level.setBlockAndUpdate(pos, blockState.setValue(ElectricArcFurnace.LIT, false));
setChanged(level, pos, blockState);
}
} else {
// Secondary slot is NOT empty, try alloying recipes
if (hasAlloyingRecipe(INPUT_SLOT, SECONDARY_INPUT_SLOT)) {
if (hasEnoughEnergyToCraft(MACHINE_ENERGY_COST)) {
// Alloy recipe detected! We have enough energy to process
progress++;
useEnergy();
level.setBlockAndUpdate(pos, blockState.setValue(ElectricArcFurnace.LIT, true));
setChanged(level, pos, blockState);
if (hasCraftingFinished()) {
outputAlloyingResult(INPUT_SLOT, SECONDARY_INPUT_SLOT, OUTPUT_SLOT);
resetProgress();
}
} else {
// Recipe detected but we ran out of power
level.setBlockAndUpdate(pos, blockState.setValue(ElectricArcFurnace.LIT, false));
setChanged(level, pos, blockState);
progress = progress > 0 ? progress - 1 : 0;
}
} else {
// Invalid recipe
resetProgress();
level.setBlockAndUpdate(pos, blockState.setValue(ElectricArcFurnace.LIT, false));
setChanged(level, pos, blockState);
}
}
}
private void chargeFromItem() {
ItemStack stack;
try {
stack = inventory.getStackInSlot(ENERGY_SLOT);
if (stack.isEmpty()) return;
IEnergyStorage itemEnergy = stack.getCapability(Capabilities.EnergyStorage.ITEM);
if (itemEnergy == null || !itemEnergy.canExtract()) return;
int freeCapacity = ENERGY_STORAGE.getMaxEnergyStored() - ENERGY_STORAGE.getEnergyStored();
if (freeCapacity <= 0) return;
int maxMove = Math.min(MAX_TRANSFER, freeCapacity);
// Simulate extraction first
int canExtract = itemEnergy.extractEnergy(maxMove, true);
if (canExtract <= 0) return;
// Receive into block (simulate then execute is safest)
int canReceive = ENERGY_STORAGE.receiveEnergy(canExtract, true);
if (canReceive <= 0) return;
int extracted = itemEnergy.extractEnergy(canReceive, false);
ENERGY_STORAGE.receiveEnergy(maxMove, false);
setChanged();
} catch (Exception e) {
}
}
private void outputBlastingResult(int slot, int resultSlot) {
Optional<RecipeHolder<BlastingRecipe>> recipe = getFurnaceRecipe(inventory.getStackInSlot(slot));
ItemStack output = recipe.get().value().getResultItem(null);
// 2x multiplier for smelting recipes
inventory.extractItem(slot, 1, false);
inventory.setStackInSlot(resultSlot, new ItemStack(output.getItem(),
inventory.getStackInSlot(resultSlot).getCount() + (output.getCount() * 2)));
}
private void outputAlloyingResult(int inputSlot, int secondaryInputSlot, int outputSlot) {
Optional<RecipeHolder<ElectricArcFurnaceRecipe>> recipe = getAlloyingRecipe(inventory.getStackInSlot(inputSlot), inventory.getStackInSlot(secondaryInputSlot));
ItemStack output = recipe.get().value().getResultItem(null);
inventory.extractItem(inputSlot, recipe.get().value().baseCount(), false);
inventory.extractItem(secondaryInputSlot, recipe.get().value().alloyCount(), false);
inventory.setStackInSlot(outputSlot, new ItemStack(output.getItem(), inventory.getStackInSlot(outputSlot).getCount() + (output.getCount())));
}
private void resetProgress() {
this.progress = 0;
this.maxProgress = DEFAULT_MAX_PROGRESS;
}
private void useEnergy() {
this.ENERGY_STORAGE.extractEnergy(MACHINE_ENERGY_COST, false);
}
private boolean hasEnoughEnergyToCraft(int energyCost) {
return ENERGY_STORAGE.getEnergyStored() >= energyCost;
}
private boolean canInsertItemIntoOutputSlot(ItemStack output, int slot) {
return inventory.getStackInSlot(slot).isEmpty() ||
inventory.getStackInSlot(slot).getItem() == output.getItem();
}
private boolean canInsertAmountIntoOutputSlot(int count, int slot) {
int maxCount = inventory.getStackInSlot(slot).isEmpty() ? 64 : inventory.getStackInSlot(slot).getMaxStackSize();
int currentCount = inventory.getStackInSlot(slot).getCount();
return maxCount >= currentCount + count;
}
private boolean isOutputSlotEmptyOrReceivable(int slot) {
return this.inventory.getStackInSlot(slot).isEmpty() ||
this.inventory.getStackInSlot(slot).getCount() < this.inventory.getStackInSlot(slot).getMaxStackSize();
}
private boolean hasCraftingFinished() {
maxProgress = DEFAULT_MAX_PROGRESS;
return this.progress >= this.maxProgress;
}
private boolean hasAlloyingRecipe(int slotBase, int slotAlloy) {
ItemStack baseStack = inventory.getStackInSlot(slotBase);
ItemStack alloyStack = inventory.getStackInSlot(slotAlloy);
Optional<RecipeHolder<ElectricArcFurnaceRecipe>> opt =
getAlloyingRecipe(baseStack, alloyStack);
if (opt.isEmpty()) return false;
ElectricArcFurnaceRecipe recipe = opt.get().value();
// 1) Check required input counts
if (baseStack.getCount() < recipe.baseCount()) return false;
if (alloyStack.getCount() < recipe.alloyCount()) return false;
// 2) Check output slot compatibility + space
ItemStack output = recipe.getResultItem(null);
maxProgress = recipe.cookTime();
return canInsertItemIntoOutputSlot(output, OUTPUT_SLOT)
&& canInsertAmountIntoOutputSlot(output.getCount(), OUTPUT_SLOT);
}
private Optional<RecipeHolder<ElectricArcFurnaceRecipe>> getAlloyingRecipe(ItemStack base, ItemStack alloy) {
if (base.isEmpty()) return Optional.empty();
if (alloy.isEmpty()) return Optional.empty();
return this.level.getRecipeManager()
.getRecipeFor(
ModRecipes.ELECTRIC_ARC_FURNACE_RECIPE_TYPE.get(),
new ElectricArcFurnaceRecipeInput(base, alloy),
level
);
}
private boolean hasFurnaceRecipe(int slot) {
Optional<RecipeHolder<BlastingRecipe>> recipe = getFurnaceRecipe(new ItemStack(inventory.getStackInSlot(slot).getItem().asItem(), 1));
if (recipe.isEmpty())
return false;
ItemStack output = recipe.get().value().getResultItem(null);
return canInsertAmountIntoOutputSlot(output.getCount() * 2, OUTPUT_SLOT) && canInsertItemIntoOutputSlot(output, OUTPUT_SLOT);
}
private Optional<RecipeHolder<BlastingRecipe>> getFurnaceRecipe(ItemStack stack) {
if (stack.isEmpty()) return Optional.empty();
return this.level.getRecipeManager()
.getRecipeFor(RecipeType.BLASTING, new SingleRecipeInput(stack), level);
}
public void sendUpdate() {
setChanged();
level.sendBlockUpdated(getBlockPos(), getBlockState(), getBlockState(), 3);
}
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);
private final IItemHandler outputHandler = new SidedSlotHandler(inventory, new int[]{2,3}, false, true);
private final IItemHandler jadeHandler = new SidedSlotHandler(inventory, new int[]{0}, false, false);
public IItemHandler getItemHandler(Direction direction) {
if (direction != null)
return isFormed() ? fullHandler : null;
return isFormed() ? fullHandler : emptyJeiHandler;
}
public IEnergyStorage getEnergyStorage(@Nullable Direction direction) {
if (direction == null)
return isFormed() ? ENERGY_STORAGE : NULL_ENERGY_STORAGE;
return isFormed() ? ENERGY_STORAGE : null;
}
public IEnergyStorage getTrueEnergyStorage(@Nullable Direction direction) {
return this.ENERGY_STORAGE;
}
private final ModEnergyStorage ENERGY_STORAGE = createEnergyStorage(ENERGY_CAPACITY, MAX_TRANSFER);
private final ModEnergyStorage NULL_ENERGY_STORAGE = createEnergyStorage(0, 0);
private ModEnergyStorage createEnergyStorage(int capacity, int transfer) {
return new ModEnergyStorage(capacity, transfer) {
@Override
public void onEnergyChanged() {
setChanged();
getLevel().sendBlockUpdated(getBlockPos(), getBlockState(), getBlockState(), 3);
}
};
}
public ItemStackHandler getInventory() {
return inventory;
}
public void drops() {
SimpleContainer inv = new SimpleContainer(inventory.getSlots());
for(int i = 0; i < inventory.getSlots(); i++) {
inv.setItem(i, inventory.getStackInSlot(i));
}
Containers.dropContents(this.level, this.worldPosition, inv);
}
@Override
protected void saveAdditional(CompoundTag tag, HolderLookup.Provider pRegistries) {
tag.put("inventory", inventory.serializeNBT(pRegistries));
tag.putInt("electric_arc_furnace.progress", progress);
tag.putInt("electric_arc_furnace.maxProgress", maxProgress);
tag.putInt("electric_arc_furnace.energy", ENERGY_STORAGE.getEnergyStored());
super.saveAdditional(tag, pRegistries);
}
@Override
protected void loadAdditional(CompoundTag tag, HolderLookup.Provider pRegistries) {
super.loadAdditional(tag, pRegistries);
inventory.deserializeNBT(pRegistries, tag.getCompound("inventory"));
progress = tag.getInt("electric_arc_furnace.progress");
maxProgress = tag.getInt("electric_arc_furnace.maxProgress");
ENERGY_STORAGE.setEnergy(tag.getInt("electric_arc_furnace.energy"));
}
@Override
public Component getDisplayName() {
return null;
}
@Override
public @Nullable AbstractContainerMenu createMenu(int i, Inventory inventory, Player player) {
if (isFormed())
return new VacuumArcFurnaceMenu(i, inventory, this, this.data);
return new ElectricArcFurnaceMenu(i, inventory, this, this.data);
}
@Nullable
@Override
public Packet<ClientGamePacketListener> getUpdatePacket() {
return ClientboundBlockEntityDataPacket.create(this);
}
@Override
public CompoundTag getUpdateTag(HolderLookup.Provider pRegistries) {
return saveWithoutMetadata(pRegistries);
}
private static BlockPos rotateY(BlockPos off, Direction facing) {
// Assumes "default" shape faces NORTH.
return switch (facing) {
case NORTH -> off;
case EAST -> new BlockPos(-off.getZ(), off.getY(), off.getX());
case SOUTH -> new BlockPos(-off.getX(), off.getY(), -off.getZ());
case WEST -> new BlockPos(off.getZ(), off.getY(), -off.getX());
default -> off;
};
}
public static final MultiblockHelper.ShapePart[] SHAPE = new MultiblockHelper.ShapePart[] {
//Layer -1
new MultiblockHelper.ShapePart(new BlockPos(1,-1,0), s -> s.is(ModBlocks.ARC_FURNACE_CASING_BLOCK)),
new MultiblockHelper.ShapePart(new BlockPos(1,-1,1), s -> s.is(ModBlocks.ARC_FURNACE_CASING_BLOCK)),
new MultiblockHelper.ShapePart(new BlockPos(1,-1,2), s -> s.is(ModBlocks.ARC_FURNACE_CASING_BLOCK)),
new MultiblockHelper.ShapePart(new BlockPos(-1,-1,0), s -> s.is(ModBlocks.ARC_FURNACE_CASING_BLOCK)),
new MultiblockHelper.ShapePart(new BlockPos(-1,-1,1), s -> s.is(ModBlocks.ARC_FURNACE_CASING_BLOCK)),
new MultiblockHelper.ShapePart(new BlockPos(-1,-1,2), s -> s.is(ModBlocks.ARC_FURNACE_CASING_BLOCK)),
new MultiblockHelper.ShapePart(new BlockPos(0,-1,0), s -> s.is(ModBlocks.ARC_FURNACE_CASING_BLOCK)),
new MultiblockHelper.ShapePart(new BlockPos(0,-1,1), s -> s.is(ModBlocks.ARC_FURNACE_CASING_BLOCK)),
new MultiblockHelper.ShapePart(new BlockPos(0,-1,2), s -> s.is(ModBlocks.ARC_FURNACE_CASING_BLOCK)),
//Layer 0
new MultiblockHelper.ShapePart(new BlockPos(1,0,0), s -> s.is(ModBlocks.ARC_FURNACE_CASING_BLOCK)),
new MultiblockHelper.ShapePart(new BlockPos(1,0,1), s -> s.is(ModBlocks.ARC_FURNACE_CASING_BLOCK)),
new MultiblockHelper.ShapePart(new BlockPos(1,0,2), s -> s.is(ModBlocks.ARC_FURNACE_CASING_BLOCK)),
new MultiblockHelper.ShapePart(new BlockPos(-1,0,0), s -> s.is(ModBlocks.ARC_FURNACE_CASING_BLOCK)),
new MultiblockHelper.ShapePart(new BlockPos(-1,0,1), s -> s.is(ModBlocks.ARC_FURNACE_CASING_BLOCK)),
new MultiblockHelper.ShapePart(new BlockPos(-1,0,2), s -> s.is(ModBlocks.ARC_FURNACE_CASING_BLOCK)),
new MultiblockHelper.ShapePart(new BlockPos(0,0,2), s -> s.is(ModBlocks.ARC_FURNACE_CASING_BLOCK)),
new MultiblockHelper.ShapePart(new BlockPos(0,0,1), s -> s.is(Blocks.AIR)),
//Layer 1
new MultiblockHelper.ShapePart(new BlockPos(1,1,0), s -> s.is(ModBlocks.ARC_FURNACE_CASING_BLOCK)),
new MultiblockHelper.ShapePart(new BlockPos(1,1,1), s -> s.is(ModBlocks.ARC_FURNACE_CASING_BLOCK)),
new MultiblockHelper.ShapePart(new BlockPos(1,1,2), s -> s.is(ModBlocks.ARC_FURNACE_CASING_BLOCK)),
new MultiblockHelper.ShapePart(new BlockPos(-1,1,0), s -> s.is(ModBlocks.ARC_FURNACE_CASING_BLOCK)),
new MultiblockHelper.ShapePart(new BlockPos(-1,1,1), s -> s.is(ModBlocks.ARC_FURNACE_CASING_BLOCK)),
new MultiblockHelper.ShapePart(new BlockPos(-1,1,2), s -> s.is(ModBlocks.ARC_FURNACE_CASING_BLOCK)),
new MultiblockHelper.ShapePart(new BlockPos(0,1,0), s -> s.is(ModBlocks.ARC_FURNACE_CASING_BLOCK)),
new MultiblockHelper.ShapePart(new BlockPos(0,1,1), s -> s.is(ModBlocks.ARC_FURNACE_CASING_BLOCK)),
new MultiblockHelper.ShapePart(new BlockPos(0,1,2), s -> s.is(ModBlocks.ARC_FURNACE_CASING_BLOCK)),
// new MultiblockHelper.ShapePart(new BlockPos(-1,0,0), s -> s.is(Blocks.AMETHYST_BLOCK))
};
/// MULTIBLOCK
/// LOGIC
/// BELOW
// record ShapePart (BlockPos offset, Predicate<BlockState> rule) {}
private boolean formed = false; // cached runtime state
// Assumes the default state is NORTH.
// (Left -Right, Up -Down, Back -Front)
// private static final BlockPos[] SHAPE = new BlockPos[] {
// new BlockPos(1, 0, 0),
// new BlockPos(0, 0, 1),
// new BlockPos(1, 0, 1),
//
// new BlockPos(0, 1, 0),
// new BlockPos(1, 1, 0),
// new BlockPos(0, 1, 1),
// new BlockPos(1, 1, 1),
// };
public boolean isFormed() {
return getBlockState().hasProperty(AphelionBlockStateProperties.FORMED)
&& getBlockState().getValue(AphelionBlockStateProperties.FORMED);
}
@Override
public void setFormed(boolean formed) {
this.formed = formed;
invalidateCapabilities();
if (!formed) {
drops();
}
}
private void setFormedState(boolean value) {
BlockState state = getBlockState();
if (state.hasProperty(ElectricArcFurnace.FORMED) && state.getValue(ElectricArcFurnace.FORMED) != value) {
level.setBlock(worldPosition, state.setValue(ElectricArcFurnace.FORMED, value), 3);
}
}
}

View File

@@ -4,10 +4,7 @@ import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.neoforged.neoforge.registries.DeferredRegister;
import net.xevianlight.aphelion.Aphelion;
import net.xevianlight.aphelion.block.entity.custom.DimensionChangerBlockEntity;
import net.xevianlight.aphelion.block.entity.custom.EAFPartEntity;
import net.xevianlight.aphelion.block.entity.custom.ElectricArcFurnaceEntity;
import net.xevianlight.aphelion.block.entity.custom.TestBlockEntity;
import net.xevianlight.aphelion.block.entity.custom.*;
import java.util.function.Supplier;
@@ -33,4 +30,9 @@ public class ModBlockEntities {
BLOCK_ENTITIES.register("eaf_part_entity", () -> BlockEntityType.Builder.of(
EAFPartEntity::new, ModBlocks.ARC_FURNACE_CASING_BLOCK.get()).build(null)
);
public static final Supplier<BlockEntityType<VacuumArcFurnaceControllerEntity>> VACUUM_ARC_FURNACE_ENTITY =
BLOCK_ENTITIES.register("vacuum_arc_furnace_controller_entity", () -> BlockEntityType.Builder.of(
VacuumArcFurnaceControllerEntity::new, ModBlocks.VACUUM_ARC_FURNACE_CONTROLLER.get()).build(null)
);
}

View File

@@ -14,4 +14,5 @@ public class ModBlocks {
public static final DeferredBlock<Block> DIMENSION_CHANGER = BLOCKS.register("dimension_changer", () -> new DimensionChangerBlock(DimensionChangerBlock.getProperties()));
public static final DeferredBlock<Block> ELECTRIC_ARC_FURNACE = BLOCKS.register("electric_arc_furnace", () -> new ElectricArcFurnace(ElectricArcFurnace.getProperties()));
public static final DeferredBlock<Block> ARC_FURNACE_CASING_BLOCK = BLOCKS.register("arc_furnace_casing", () -> new ArcFurnaceCasingBlock(ArcFurnaceCasingBlock.getProperties()));
public static final DeferredBlock<Block> VACUUM_ARC_FURNACE_CONTROLLER = BLOCKS.register("vacuum_arc_furnace_controller", () -> new VacuumArcFurnaceController(VacuumArcFurnaceController.getProperties()));
}

View File

@@ -33,4 +33,5 @@ public static final DeferredItem<Item> MUSIC_DISC_BIT_SHIFT = ITEMS.register("mu
public static final DeferredItem<BlockItem> ELECTRIC_ARC_FURNACE = ITEMS.register("electric_arc_furnace", () -> new BlockItem(ModBlocks.ELECTRIC_ARC_FURNACE.get(), ElectricArcFurnace.getItemProperties()));
public static final DeferredItem<BlockItem> BLOCK_STEEL = ITEMS.register("block_steel", () -> new BlockItem(ModBlocks.BLOCK_STEEL.get(), BlockSteel.getItemProperties()));
public static final DeferredItem<BlockItem> ARC_FURNACE_CASING_BLOCK = ITEMS.register("arc_furnace_casing", () -> new BlockItem(ModBlocks.ARC_FURNACE_CASING_BLOCK.get(), ArcFurnaceCasingBlock.getItemProperties()));
public static final DeferredItem<BlockItem> VACUUM_ARC_FURNACE_CONTROLLER = ITEMS.register("vacuum_arc_furnace_controller", () -> new BlockItem(ModBlocks.VACUUM_ARC_FURNACE_CONTROLLER.get(), VacuumArcFurnaceController.getItemProperties()));
}

View File

@@ -21,6 +21,7 @@ public class ModBlockLootTableProvider extends BlockLootSubProvider {
dropSelf(ModBlocks.DIMENSION_CHANGER.get());
dropSelf(ModBlocks.ELECTRIC_ARC_FURNACE.get());
dropSelf(ModBlocks.ARC_FURNACE_CASING_BLOCK.get());
dropSelf(ModBlocks.VACUUM_ARC_FURNACE_CONTROLLER.get());
}
@Override

View File

@@ -18,11 +18,12 @@ public class ModBlockStateProvider extends BlockStateProvider {
protected void registerStatesAndModels() {
blockWithItem(ModBlocks.TEST_BLOCK);
// horizontalBlock(ModBlocks.ELECTRIC_ARC_FURNACE.get(), models().orientable("aphelion:electric_arc_furnace",
// mcLoc("block/blast_furnace_side"),
// modLoc("block/electric_arc_furnace_front"),
// mcLoc("block/blast_furnace_top")));
horizontalBlock(ModBlocks.ELECTRIC_ARC_FURNACE.get(), models().orientable("aphelion:electric_arc_furnace",
mcLoc("block/blast_furnace_side"),
modLoc("block/electric_arc_furnace_front"),
mcLoc("block/blast_furnace_top")));
blockItem(ModBlocks.ELECTRIC_ARC_FURNACE);
blockItem(ModBlocks.VACUUM_ARC_FURNACE_CONTROLLER);
blockWithItem(ModBlocks.BLOCK_STEEL);
blockWithItem(ModBlocks.DIMENSION_CHANGER);

View File

@@ -11,6 +11,7 @@ import net.neoforged.neoforge.network.registration.PayloadRegistrar;
import net.xevianlight.aphelion.Aphelion;
import net.xevianlight.aphelion.block.entity.custom.ElectricArcFurnaceEntity;
import net.xevianlight.aphelion.block.entity.custom.TestBlockEntity;
import net.xevianlight.aphelion.block.entity.custom.VacuumArcFurnaceControllerEntity;
import net.xevianlight.aphelion.core.init.ModBlockEntities;
import net.xevianlight.aphelion.network.ServerPayloadHandler;
import net.xevianlight.aphelion.network.packet.PartitionData;
@@ -22,6 +23,8 @@ public class ModBusEvents {
event.registerBlockEntity(Capabilities.ItemHandler.BLOCK, ModBlockEntities.TEST_BLOCK_ENTITY.get(), TestBlockEntity::getItemHandler);
event.registerBlockEntity(Capabilities.ItemHandler.BLOCK, ModBlockEntities.ELECTRIC_ARC_FURNACE_ENTITY.get(), ElectricArcFurnaceEntity::getItemHandler);
event.registerBlockEntity(Capabilities.EnergyStorage.BLOCK, ModBlockEntities.ELECTRIC_ARC_FURNACE_ENTITY.get(), ElectricArcFurnaceEntity::getEnergyStorage);
event.registerBlockEntity(Capabilities.ItemHandler.BLOCK, ModBlockEntities.VACUUM_ARC_FURNACE_ENTITY.get(), VacuumArcFurnaceControllerEntity::getItemHandler);
event.registerBlockEntity(Capabilities.EnergyStorage.BLOCK, ModBlockEntities.VACUUM_ARC_FURNACE_ENTITY.get(), VacuumArcFurnaceControllerEntity::getEnergyStorage);
}
@SubscribeEvent

View File

@@ -1,9 +1,6 @@
package net.xevianlight.aphelion.screen;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.Container;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
@@ -14,7 +11,6 @@ import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.neoforged.neoforge.items.SlotItemHandler;
import net.xevianlight.aphelion.Aphelion;
import net.xevianlight.aphelion.block.entity.custom.ElectricArcFurnaceEntity;
import net.xevianlight.aphelion.util.EnergyItemSlot;
import net.xevianlight.aphelion.util.ExtractOnlySlot;

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.getEnergyStorage(null).getEnergyStored();
int max = menu.blockEntity.getEnergyStorage(null).getMaxEnergyStored();
int stored = menu.blockEntity.getTrueEnergyStorage(null).getEnergyStored();
int max = menu.blockEntity.getTrueEnergyStorage(null).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.getEnergyStorage(null), 14, 42);
((height - imageHeight) / 2 ) + 9, menu.blockEntity.getTrueEnergyStorage(null), 14, 42);
}
private void renderEnergyAreaTooltip(GuiGraphics guiGraphics, int pMouseX, int pMouseY, int x, int y) {

View File

@@ -16,9 +16,13 @@ public class ModMenuTypes {
public static DeferredHolder<MenuType<?>,MenuType<TestBlockMenu>> TEST_BLOCK_MENU =
registerMenuType("test_block_menu", TestBlockMenu::new);
public static DeferredHolder<MenuType<?>,MenuType<ElectricArcFurnaceMenu>> ELECTRIC_ARC_FURNACE_MENU =
registerMenuType("electric_arc_furnace_menu", ElectricArcFurnaceMenu::new);
public static DeferredHolder<MenuType<?>,MenuType<VacuumArcFurnaceMenu>> VACUUM_ARC_FURNACE_MENU =
registerMenuType("vacuum_arc_furnace_menu", VacuumArcFurnaceMenu::new);
private static <T extends AbstractContainerMenu>DeferredHolder<MenuType<?>, MenuType<T>> registerMenuType(String name,
IContainerFactory<T> factory) {
return MENUS.register(name, () -> IMenuTypeExtension.create(factory));

View File

@@ -0,0 +1,129 @@
package net.xevianlight.aphelion.screen;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.ContainerData;
import net.minecraft.world.inventory.SimpleContainerData;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
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.custom.VacuumArcFurnaceController;
import net.xevianlight.aphelion.block.entity.custom.ElectricArcFurnaceEntity;
import net.xevianlight.aphelion.block.entity.custom.VacuumArcFurnaceControllerEntity;
import net.xevianlight.aphelion.util.EnergyItemSlot;
import net.xevianlight.aphelion.util.ExtractOnlySlot;
import org.jetbrains.annotations.NotNull;
public class VacuumArcFurnaceMenu extends AbstractContainerMenu {
public final VacuumArcFurnaceControllerEntity blockEntity;
private final Level level;
private final ContainerData data;
public VacuumArcFurnaceMenu(int i, Inventory inventory, FriendlyByteBuf extraData) {
this(i, inventory, inventory.player.level().getBlockEntity(extraData.readBlockPos()), new SimpleContainerData(4));
}
public VacuumArcFurnaceMenu(int i, Inventory inventory, BlockEntity blockEntity, ContainerData data) {
super(ModMenuTypes.VACUUM_ARC_FURNACE_MENU.get(), i);
this.blockEntity = ((VacuumArcFurnaceControllerEntity) blockEntity);
this.level = inventory.player.level();
this.data = data;
addPlayerInventory(inventory);
addPlayerHotbar(inventory);
this.addSlot(new EnergyItemSlot(this.blockEntity.inventory, VacuumArcFurnaceControllerEntity.ENERGY_SLOT, 8, 54));
this.addSlot(new SlotItemHandler(this.blockEntity.inventory, VacuumArcFurnaceControllerEntity.INPUT_SLOT, 63, 35));
this.addSlot(new SlotItemHandler(this.blockEntity.inventory, VacuumArcFurnaceControllerEntity.SECONDARY_INPUT_SLOT, 40, 35));
this.addSlot(new ExtractOnlySlot(this.blockEntity.inventory, VacuumArcFurnaceControllerEntity.OUTPUT_SLOT, 125, 35));
addDataSlots(data);
}
@Override
public boolean stillValid(Player player) {
return true;
}
// CREDIT GOES TO: diesieben07 | https://github.com/diesieben07/SevenCommons
// must assign a slot number to each of the slots used by the GUI.
// For this container, we can see both the tile inventory's slots as well as the player inventory slots and the hotbar.
// Each time we add a Slot to the container, it automatically increases the slotIndex, which means
// 0 - 8 = hotbar slots (which will map to the InventoryPlayer slot numbers 0 - 8)
// 9 - 35 = player inventory slots (which map to the InventoryPlayer slot numbers 9 - 35)
// 36 - 44 = TileInventory slots, which map to our TileEntity slot numbers 0 - 8)
private static final int HOTBAR_SLOT_COUNT = 9;
private static final int PLAYER_INVENTORY_ROW_COUNT = 3;
private static final int PLAYER_INVENTORY_COLUMN_COUNT = 9;
private static final int PLAYER_INVENTORY_SLOT_COUNT = PLAYER_INVENTORY_COLUMN_COUNT * PLAYER_INVENTORY_ROW_COUNT;
private static final int VANILLA_SLOT_COUNT = HOTBAR_SLOT_COUNT + PLAYER_INVENTORY_SLOT_COUNT;
private static final int VANILLA_FIRST_SLOT_INDEX = 0;
private static final int TE_INVENTORY_FIRST_SLOT_INDEX = VANILLA_FIRST_SLOT_INDEX + VANILLA_SLOT_COUNT;
// THIS YOU HAVE TO DEFINE!
private static final int TE_INVENTORY_SLOT_COUNT = 3; // must be the number of slots you have!
@Override
public @NotNull ItemStack quickMoveStack(Player playerIn, int pIndex) {
Slot sourceSlot = slots.get(pIndex);
if (sourceSlot == null || !sourceSlot.hasItem()) return ItemStack.EMPTY; //EMPTY_ITEM
ItemStack sourceStack = sourceSlot.getItem();
ItemStack copyOfSourceStack = sourceStack.copy();
// Check if the slot clicked is one of the vanilla container slots
if (pIndex < VANILLA_FIRST_SLOT_INDEX + VANILLA_SLOT_COUNT) {
// This is a vanilla container slot so merge the stack into the tile inventory
if (!moveItemStackTo(sourceStack, TE_INVENTORY_FIRST_SLOT_INDEX, TE_INVENTORY_FIRST_SLOT_INDEX
+ TE_INVENTORY_SLOT_COUNT, false)) {
blockEntity.sendUpdate();
return ItemStack.EMPTY; // EMPTY_ITEM
}
} else if (pIndex < TE_INVENTORY_FIRST_SLOT_INDEX + TE_INVENTORY_SLOT_COUNT) {
// This is a TE slot so merge the stack into the players inventory
if (!moveItemStackTo(sourceStack, VANILLA_FIRST_SLOT_INDEX, VANILLA_FIRST_SLOT_INDEX + VANILLA_SLOT_COUNT, false)) {
blockEntity.sendUpdate();
return ItemStack.EMPTY;
}
} else {
System.out.println("Invalid slotIndex:" + pIndex);
return ItemStack.EMPTY;
}
// If stack size == 0 (the entire stack was moved) set slot contents to null
if (sourceStack.getCount() == 0) {
sourceSlot.set(ItemStack.EMPTY);
blockEntity.sendUpdate();
} else {
blockEntity.sendUpdate();
sourceSlot.setChanged();
}
sourceSlot.onTake(playerIn, sourceStack);
blockEntity.sendUpdate();
return copyOfSourceStack;
}
public int getScaledArrowProgress() {
int progress = this.data.get(0);
int maxProgress = this.data.get(1);
int arrowPixelSize = 24;
return maxProgress != 0 && progress != 0 ? progress * arrowPixelSize / maxProgress : 0;
}
private void addPlayerInventory(Inventory playerInventory) {
for (int i = 0; i < 3; ++i) {
for (int l = 0; l < 9; ++l) {
this.addSlot(new Slot(playerInventory, l + i * 9 + 9, 8 + l * 18, 84 + i * 18));
}
}
}
private void addPlayerHotbar(Inventory playerInventory) {
for (int i = 0; i < 9; ++i) {
this.addSlot(new Slot(playerInventory, i, 8 + i * 18, 142));
}
}
}

View File

@@ -0,0 +1,111 @@
package net.xevianlight.aphelion.screen;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.xevianlight.aphelion.Aphelion;
import net.xevianlight.aphelion.screen.renderer.EnergyDisplayTooltipArea;
import net.xevianlight.aphelion.util.MouseUtil;
import java.util.Optional;
public class VacuumArcFurnaceScreen extends AbstractContainerScreen<VacuumArcFurnaceMenu> {
private static final ResourceLocation GUI_TEXTURE =
ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID, "textures/gui/vacuum_arc_furnace/gui.png");
private static final ResourceLocation ARROW_TEXTURE =
ResourceLocation.fromNamespaceAndPath(Aphelion.MOD_ID,"textures/gui/base/arrow_progress.png");
private EnergyDisplayTooltipArea energyInfoArea;
public VacuumArcFurnaceScreen(VacuumArcFurnaceMenu menu, Inventory playerInventory, Component title) {
super(menu, playerInventory, title);
}
@Override
protected void init() {
super.init();
// Gets rid of labels
this.inventoryLabelY = 73;
this.titleLabelY = 5;
this.titleLabelX = 35;
assignEnergyInfoArea();
}
@Override
protected void renderBg(GuiGraphics guiGraphics, float partialTick, int mouseX, int mouseY) {
RenderSystem.setShader(GameRenderer::getPositionTexShader);
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
RenderSystem.setShaderTexture(0, GUI_TEXTURE);
int x = (width - imageWidth) / 2;
int y = (height - imageHeight) / 2;
guiGraphics.blit(GUI_TEXTURE, x, y, 0, 0, imageWidth, imageHeight);
renderProgressArrow(guiGraphics, x, y);
if (menu.blockEntity.getBlockState().getValue(BlockStateProperties.LIT)) {
guiGraphics.blit(GUI_TEXTURE, x + 54, y + 14, 176, 75, 13, 18);
}
renderEnergyBar(guiGraphics, x , y);
}
private void renderProgressArrow(GuiGraphics guiGraphics, int x, int y) {
guiGraphics.blit(ARROW_TEXTURE,x + 88, y + 35, 0, 0, menu.getScaledArrowProgress(), 16, 24, 16);
}
private void renderEnergyBar(GuiGraphics guiGraphics, int x, int y) {
int stored = menu.blockEntity.getTrueEnergyStorage(null).getEnergyStored();
int max = menu.blockEntity.getTrueEnergyStorage(null).getMaxEnergyStored();
int h = (max <= 0) ? 0 : (int) Math.round((stored / (double) max) * 42.0);
h = Math.max(0, Math.min(42, h));
int w = 14;
int drawX = x + 9;
int drawY = y + 9 + (42 - h); // move up as it fills
int u = 176;
int v = 15 + (42 - h); // sample lower part of bar texture
guiGraphics.blit(GUI_TEXTURE, drawX, drawY, u, v, w, h);
}
private void assignEnergyInfoArea() {
energyInfoArea = new EnergyDisplayTooltipArea(((width - imageWidth) / 2) + 9,
((height - imageHeight) / 2 ) + 9, menu.blockEntity.getTrueEnergyStorage(null), 14, 42);
}
private void renderEnergyAreaTooltip(GuiGraphics guiGraphics, int pMouseX, int pMouseY, int x, int y) {
if(isMouseAboveArea(pMouseX, pMouseY, x, y, 9, 9, 14, 42)) {
guiGraphics.renderTooltip(this.font, energyInfoArea.getTooltips(),
Optional.empty(), pMouseX - x, pMouseY - y);
}
}
@Override
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float delta) {
renderBackground(guiGraphics, mouseX, mouseY, delta);
super.render(guiGraphics, mouseX, mouseY, delta);
renderTooltip(guiGraphics, mouseX, mouseY);
}
@Override
protected void renderLabels(GuiGraphics guiGraphics, int mouseX, int mouseY) {
super.renderLabels(guiGraphics, mouseX, mouseY);
int x = (width - imageWidth) / 2;
int y = (height - imageHeight) / 2;
renderEnergyAreaTooltip(guiGraphics, mouseX, mouseY, x, y);
}
public static boolean isMouseAboveArea(int pMouseX, int pMouseY, int x, int y, int offsetX, int offsetY, int width, int height) {
return MouseUtil.isMouseOver(pMouseX, pMouseY, x + offsetX, y + offsetY, width, height);
}
}

View File

@@ -0,0 +1,8 @@
package net.xevianlight.aphelion.util;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
public class AphelionBlockStateProperties extends BlockStateProperties {
public static final BooleanProperty FORMED = BooleanProperty.create("formed");
}

View File

@@ -0,0 +1,9 @@
package net.xevianlight.aphelion.util;
import net.minecraft.core.BlockPos;
import org.jetbrains.annotations.Nullable;
public interface IMultiblockController {
boolean isFormed();
void setFormed(boolean formed);
}

View File

@@ -0,0 +1,10 @@
package net.xevianlight.aphelion.util;
import net.minecraft.core.BlockPos;
import org.jetbrains.annotations.Nullable;
public interface IMultiblockPart {
@Nullable BlockPos getControllerPos();
void setControllerPos(@Nullable BlockPos pos);
}

View File

@@ -0,0 +1,192 @@
package net.xevianlight.aphelion.util;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.Level;
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.BooleanProperty;
import net.xevianlight.aphelion.Aphelion;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public class MultiblockHelper {
public record ShapePart (BlockPos offset, Predicate<BlockState> rule) {}
public static List<BlockPos> getPartPositions(BlockState state, BlockPos pos, ShapePart[] shape) {
Direction facing = state.getValue(BlockStateProperties.HORIZONTAL_FACING);
List<BlockPos> out = new ArrayList<>(shape.length);
for (ShapePart part : shape) {
BlockPos rotate = rotateY(part.offset, facing);
out.add(pos.offset(rotate));
}
return out;
}
private static BlockPos rotateY(BlockPos offset, Direction facing) {
// Assumes default shape faces north
return switch (facing) {
case NORTH -> offset;
case EAST -> new BlockPos(-offset.getZ(), offset.getY(), offset.getX());
case SOUTH -> new BlockPos(-offset.getX(), offset.getY(), -offset.getZ());
case WEST -> new BlockPos(offset.getZ(), offset.getY(), -offset.getX());
default -> offset;
};
}
private static boolean structureMatches(Level level, BlockState state, BlockPos pos, ShapePart[] shape) {
if (level == null || level.isClientSide) return false;
Direction facing = state.getValue(BlockStateProperties.HORIZONTAL_FACING);
for (ShapePart part : shape) {
BlockPos testPos = pos.offset(rotateY(part.offset(), facing));
BlockState check = level.getBlockState(testPos);
if (!part.rule().test(check)) {
return false;
}
}
return true;
}
public static void tryForm(Level level, BlockState state, BlockPos pos, ShapePart[] shape, @Nullable BooleanProperty formedProp) {
if (level == null || level.isClientSide) return;
structureMatchesDebug(level, state, pos, shape);
BlockEntity be = level.getBlockEntity(pos);
if (!(be instanceof IMultiblockController controller)) return;
boolean valid = structureMatches(level, state, pos, shape);
if (valid && !controller.isFormed()) {
controller.setFormed(true);
linkParts(level, state, pos, shape, formedProp);
if (state.hasProperty(AphelionBlockStateProperties.FORMED) && !state.getValue(AphelionBlockStateProperties.FORMED)) {
level.setBlock(pos, state.setValue(AphelionBlockStateProperties.FORMED, true), 3);
}
} else if (!valid && controller.isFormed()) {
unform(level, state, pos, shape, formedProp);
}
}
public static void unform(Level level, BlockState state, BlockPos pos, ShapePart[] shape, @Nullable BooleanProperty formedProp) {
if (level == null || level.isClientSide) return;
BlockEntity be = level.getBlockEntity(pos);
if (!(be instanceof IMultiblockController controller)) return;
controller.setFormed(false);
if (state.hasProperty(AphelionBlockStateProperties.FORMED) && state.getValue(AphelionBlockStateProperties.FORMED)) {
level.setBlock(pos, state.setValue(AphelionBlockStateProperties.FORMED, false), 3);
}
unlinkParts(level, state, pos, shape, formedProp);
}
public static void unformForRemoval(Level level, BlockState state, BlockPos pos, ShapePart[] shape, @Nullable BooleanProperty formedProp) {
if (level == null || level.isClientSide) return;
BlockEntity be = level.getBlockEntity(pos);
if (!(be instanceof IMultiblockController controller)) return;
controller.setFormed(false);
unlinkParts(level, state, pos, shape, formedProp);
}
private static void unlinkParts(Level level, BlockState state, BlockPos pos, ShapePart[] shape, @Nullable BooleanProperty formedProp) {
if (level == null || level.isClientSide) return;
Direction facing = state.getValue(BlockStateProperties.HORIZONTAL_FACING);
for (ShapePart part : shape) {
BlockPos partPos = pos.offset(rotateY(part.offset(), facing));
BlockState st = level.getBlockState(partPos);
if (formedProp != null && st.hasProperty(formedProp) && st.getValue(formedProp)) {
level.setBlock(partPos, st.setValue(formedProp, false), 3);
}
BlockEntity be = level.getBlockEntity(partPos);
if (be instanceof IMultiblockPart mbPart) {
if (pos.equals(mbPart.getControllerPos())) {
mbPart.setControllerPos(null);
be.setChanged();
}
}
}
}
private static void linkParts(Level level, BlockState state, BlockPos pos, ShapePart[] shape, @Nullable BooleanProperty formedProp) {
if (level == null || level.isClientSide) return;
Direction facing = state.getValue(BlockStateProperties.HORIZONTAL_FACING);
for (ShapePart part : shape) {
BlockPos partPos = pos.offset(rotateY(part.offset(), facing));
BlockState st = level.getBlockState(partPos);
Aphelion.LOGGER.debug("[Multiblock] partPos={} block={} hasFormed={} formedVal={}",
partPos,
st.getBlock(),
(formedProp != null && st.hasProperty(formedProp)),
(formedProp != null && st.hasProperty(formedProp)) ? st.getValue(formedProp) : "n/a"
);
if (formedProp != null && st.hasProperty(formedProp) && !st.getValue(formedProp)) {
level.setBlock(partPos, st.setValue(formedProp, true), 3);
st = level.getBlockState(partPos);
}
BlockEntity be = level.getBlockEntity(partPos);
if (be instanceof IMultiblockPart mbPart) {
if (!pos.equals(mbPart.getControllerPos())) {
mbPart.setControllerPos(pos);
be.setChanged();
}
}
}
}
public static boolean structureMatchesDebug(
Level level,
BlockState controllerState,
BlockPos controllerPos,
ShapePart[] shape
) {
Direction facing = controllerState.getValue(BlockStateProperties.HORIZONTAL_FACING);
for (ShapePart part : shape) {
BlockPos rotated = rotateY(part.offset(), facing);
BlockPos worldPos = controllerPos.offset(rotated);
BlockState found = level.getBlockState(worldPos);
if (!part.rule().test(found)) {
Aphelion.LOGGER.debug("[Multiblock] FAIL at offset=" + part.offset()
+ " facing=" + facing
+ " rotated=" + rotated
+ " worldPos=" + worldPos
+ " found=" + found.getBlock());
return false;
}
}
Aphelion.LOGGER.debug("[Multiblock] OK facing={} controllerPos={}", controllerState.getValue(BlockStateProperties.HORIZONTAL_FACING), controllerPos);
return true;
}
}

View File

@@ -1,64 +0,0 @@
{
"variants": {
"facing=east,formed=false,lit=false": {
"model": "aphelion:block/electric_arc_furnace",
"y": 90
},
"facing=east,formed=false,lit=true": {
"model": "aphelion:block/electric_arc_furnace",
"y": 90
},
"facing=east,formed=true,lit=false": {
"model": "aphelion:block/electric_arc_furnace_formed",
"y": 90
},
"facing=east,formed=true,lit=true": {
"model": "aphelion:block/electric_arc_furnace_formed",
"y": 90
},
"facing=north,formed=false,lit=false": {
"model": "aphelion:block/electric_arc_furnace"
},
"facing=north,formed=false,lit=true": {
"model": "aphelion:block/electric_arc_furnace"
},
"facing=north,formed=true,lit=false": {
"model": "aphelion:block/electric_arc_furnace_formed"
},
"facing=north,formed=true,lit=true": {
"model": "aphelion:block/electric_arc_furnace_formed"
},
"facing=south,formed=false,lit=false": {
"model": "aphelion:block/electric_arc_furnace",
"y": 180
},
"facing=south,formed=false,lit=true": {
"model": "aphelion:block/electric_arc_furnace",
"y": 180
},
"facing=south,formed=true,lit=false": {
"model": "aphelion:block/electric_arc_furnace_formed",
"y": 180
},
"facing=south,formed=true,lit=true": {
"model": "aphelion:block/electric_arc_furnace_formed",
"y": 180
},
"facing=west,formed=false,lit=false": {
"model": "aphelion:block/electric_arc_furnace",
"y": 270
},
"facing=west,formed=false,lit=true": {
"model": "aphelion:block/electric_arc_furnace",
"y": 270
},
"facing=west,formed=true,lit=false": {
"model": "aphelion:block/electric_arc_furnace_formed",
"y": 270
},
"facing=west,formed=true,lit=true": {
"model": "aphelion:block/electric_arc_furnace_formed",
"y": 270
}
}
}

View File

@@ -0,0 +1,64 @@
{
"variants": {
"facing=east,formed=false,lit=false": {
"model": "aphelion:block/vacuum_arc_furnace_controller",
"y": 90
},
"facing=east,formed=false,lit=true": {
"model": "aphelion:block/vacuum_arc_furnace_controller",
"y": 90
},
"facing=east,formed=true,lit=false": {
"model": "aphelion:block/vacuum_arc_furnace_controller_formed",
"y": 90
},
"facing=east,formed=true,lit=true": {
"model": "aphelion:block/vacuum_arc_furnace_controller_formed",
"y": 90
},
"facing=north,formed=false,lit=false": {
"model": "aphelion:block/vacuum_arc_furnace_controller"
},
"facing=north,formed=false,lit=true": {
"model": "aphelion:block/vacuum_arc_furnace_controller"
},
"facing=north,formed=true,lit=false": {
"model": "aphelion:block/vacuum_arc_furnace_controller_formed"
},
"facing=north,formed=true,lit=true": {
"model": "aphelion:block/vacuum_arc_furnace_controller_formed"
},
"facing=south,formed=false,lit=false": {
"model": "aphelion:block/vacuum_arc_furnace_controller",
"y": 180
},
"facing=south,formed=false,lit=true": {
"model": "aphelion:block/vacuum_arc_furnace_controller",
"y": 180
},
"facing=south,formed=true,lit=false": {
"model": "aphelion:block/vacuum_arc_furnace_controller_formed",
"y": 180
},
"facing=south,formed=true,lit=true": {
"model": "aphelion:block/vacuum_arc_furnace_controller_formed",
"y": 180
},
"facing=west,formed=false,lit=false": {
"model": "aphelion:block/vacuum_arc_furnace_controller",
"y": 270
},
"facing=west,formed=false,lit=true": {
"model": "aphelion:block/vacuum_arc_furnace_controller",
"y": 270
},
"facing=west,formed=true,lit=false": {
"model": "aphelion:block/vacuum_arc_furnace_controller_formed",
"y": 270
},
"facing=west,formed=true,lit=true": {
"model": "aphelion:block/vacuum_arc_furnace_controller_formed",
"y": 270
}
}
}

View File

@@ -1,7 +1,7 @@
{
"parent": "minecraft:block/orientable",
"textures": {
"front": "aphelion:block/electric_arc_furnace_front",
"front": "aphelion:block/vacuum_arc_furnace_controller_front",
"side": "minecraft:block/blast_furnace_side",
"top": "minecraft:block/blast_furnace_top"
}

View File

@@ -1,7 +1,7 @@
{
"parent": "minecraft:block/orientable",
"textures": {
"front": "aphelion:block/electric_arc_furnace_front_formed",
"front": "aphelion:block/vacuum_arc_furnace_controller_front_formed",
"side": "minecraft:block/blast_furnace_side",
"top": "minecraft:block/blast_furnace_top"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB