package com.simibubi.create.content.logistics.trains; import java.util.ArrayList; import java.util.Collection; import java.util.EnumSet; import java.util.List; import java.util.Optional; import java.util.Set; import javax.annotation.Nullable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; import com.mojang.math.Vector3f; import com.simibubi.create.AllBlocks; import com.simibubi.create.AllBogeyStyles; import com.simibubi.create.AllItems; import com.simibubi.create.content.contraptions.wrench.IWrenchable; import com.simibubi.create.content.logistics.trains.entity.BogeyStyle; import com.simibubi.create.content.logistics.trains.entity.Carriage; import com.simibubi.create.content.logistics.trains.entity.CarriageBogey; import com.simibubi.create.content.logistics.trains.entity.TravellingPoint; import com.simibubi.create.content.logistics.trains.track.AbstractBogeyTileEntity; import com.simibubi.create.content.schematics.ISpecialBlockItemRequirement; import com.simibubi.create.content.schematics.ItemRequirement; import com.simibubi.create.foundation.block.ITE; import com.simibubi.create.foundation.block.ProperWaterloggedBlock; import com.simibubi.create.foundation.utility.Iterate; import com.simibubi.create.foundation.utility.Lang; import com.simibubi.create.foundation.utility.RegisteredObjects; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.RenderType; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.nbt.CompoundTag; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Rotation; 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.BlockStateProperties; import net.minecraft.world.level.block.state.properties.EnumProperty; import net.minecraft.world.level.block.state.properties.Property; import net.minecraft.world.level.material.FluidState; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.Vec3; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.registries.ForgeRegistries; import org.jetbrains.annotations.NotNull; public abstract class AbstractBogeyBlock extends Block implements ITE, ProperWaterloggedBlock, ISpecialBlockItemRequirement, IWrenchable { public static final EnumProperty AXIS = BlockStateProperties.HORIZONTAL_AXIS; static final List BOGEYS = new ArrayList<>(); public BogeySizes.BogeySize size; public AbstractBogeyBlock(Properties pProperties, BogeySizes.BogeySize size) { super(pProperties); registerDefaultState(defaultBlockState().setValue(WATERLOGGED, false)); this.size = size; } public boolean isOnIncompatibleTrack(Carriage carriage, boolean leading) { TravellingPoint point = leading ? carriage.getLeadingPoint() : carriage.getTrailingPoint(); CarriageBogey bogey = leading ? carriage.leadingBogey() : carriage.trailingBogey(); return point.edge.getTrackMaterial().trackType != getTrackType(bogey.getStyle()); } public Set getValidPathfindingTypes(BogeyStyle style) { return ImmutableSet.of(getTrackType(style)); } public abstract TrackMaterial.TrackType getTrackType(BogeyStyle style); /** * Only for internal Create use. If you have your own style set, do not call this method */ @Deprecated public static void registerStandardBogey(ResourceLocation block) { BOGEYS.add(block); } @Override protected void createBlockStateDefinition(StateDefinition.Builder builder) { builder.add(AXIS, WATERLOGGED); super.createBlockStateDefinition(builder); } @Override public BlockState updateShape(BlockState pState, Direction pDirection, BlockState pNeighborState, LevelAccessor pLevel, BlockPos pCurrentPos, BlockPos pNeighborPos) { updateWater(pLevel, pState, pCurrentPos); return pState; } @Override public FluidState getFluidState(BlockState pState) { return fluidState(pState); } static final EnumSet STICKY_X = EnumSet.of(Direction.EAST, Direction.WEST); static final EnumSet STICKY_Z = EnumSet.of(Direction.SOUTH, Direction.NORTH); public EnumSet getStickySurfaces(BlockGetter world, BlockPos pos, BlockState state) { return state.getValue(BlockStateProperties.HORIZONTAL_AXIS) == Direction.Axis.X ? STICKY_X : STICKY_Z; } public abstract double getWheelPointSpacing(); public abstract double getWheelRadius(); public Vec3 getConnectorAnchorOffset(boolean upsideDown) { return getConnectorAnchorOffset(); } /** * This should be implemented, but not called directly */ protected abstract Vec3 getConnectorAnchorOffset(); public boolean allowsSingleBogeyCarriage() { return true; } public abstract BogeyStyle getDefaultStyle(); /** * Legacy system doesn't capture bogey tile entities when constructing a train */ public boolean captureTileEntityForTrain() { return false; } @OnlyIn(Dist.CLIENT) public void render(@Nullable BlockState state, float wheelAngle, PoseStack ms, float partialTicks, MultiBufferSource buffers, int light, int overlay, BogeyStyle style, CompoundTag bogeyData) { if (style == null) style = getDefaultStyle(); final Optional commonRenderer = style.getInWorldCommonRenderInstance(); final BogeyRenderer renderer = style.getInWorldRenderInstance(this.getSize()); if (state != null) { ms.translate(.5f, .5f, .5f); if (state.getValue(AXIS) == Direction.Axis.X) ms.mulPose(Vector3f.YP.rotationDegrees(90)); } ms.translate(0, -1.5 - 1 / 128f, 0); VertexConsumer vb = buffers.getBuffer(RenderType.cutoutMipped()); if (bogeyData == null) bogeyData = new CompoundTag(); renderer.render(bogeyData, wheelAngle, ms, light, vb, state == null); CompoundTag finalBogeyData = bogeyData; commonRenderer.ifPresent(common -> common.render(finalBogeyData, wheelAngle, ms, light, vb, state == null)); } public BogeySizes.BogeySize getSize() { return this.size; } public Direction getBogeyUpDirection() { return Direction.UP; } public boolean isTrackAxisAlongFirstCoordinate(BlockState state) { return state.getValue(AXIS) == Direction.Axis.X; } @Nullable public BlockState getMatchingBogey(Direction upDirection, boolean axisAlongFirst) { if (upDirection != Direction.UP) return null; return defaultBlockState().setValue(AXIS, axisAlongFirst ? Direction.Axis.X : Direction.Axis.Z); } @Override public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { if (level.isClientSide) return InteractionResult.PASS; ItemStack stack = player.getItemInHand(hand); if (!player.isShiftKeyDown() && stack.is(AllItems.WRENCH.get()) && !player.getCooldowns().isOnCooldown(stack.getItem()) && AllBogeyStyles.BOGEY_STYLES.size() > 1) { BlockEntity be = level.getBlockEntity(pos); if (!(be instanceof AbstractBogeyTileEntity sbte)) return InteractionResult.FAIL; player.getCooldowns().addCooldown(stack.getItem(), 20); BogeyStyle currentStyle = sbte.getStyle(); BogeySizes.BogeySize size = getSize(); BogeyStyle style = this.getNextStyle(currentStyle); if (style == currentStyle) return InteractionResult.PASS; Set validSizes = style.validSizes(); for (int i = 0; i < BogeySizes.count(); i++) { if (validSizes.contains(size)) break; size = size.increment(); } sbte.setBogeyStyle(style); CompoundTag defaultData = style.defaultData; sbte.setBogeyData(sbte.getBogeyData().merge(defaultData)); if (size == getSize()) { player.displayClientMessage(Lang.translateDirect("bogey.style.updated_style") .append(": ").append(style.displayName), true); } else { CompoundTag oldData = sbte.getBogeyData(); level.setBlock(pos, this.getStateOfSize(sbte, size), 3); BlockEntity newBlockEntity = level.getBlockEntity(pos); if (!(newBlockEntity instanceof AbstractBogeyTileEntity newTileEntity)) return InteractionResult.FAIL; newTileEntity.setBogeyData(oldData); player.displayClientMessage(Lang.translateDirect("bogey.style.updated_style_and_size") .append(": ").append(style.displayName), true); } return InteractionResult.CONSUME; } return InteractionResult.PASS; } /** * If, instead of using the style-based cycling system you prefer to use separate blocks, return them from this method */ protected List getBogeyBlockCycle() { return BOGEYS; } @Override public BlockState getRotatedBlockState(BlockState state, Direction targetedFace) { Block block = state.getBlock(); List bogeyCycle = getBogeyBlockCycle(); int indexOf = bogeyCycle.indexOf(RegisteredObjects.getKeyOrThrow(block)); if (indexOf == -1) return state; int index = (indexOf + 1) % bogeyCycle.size(); Direction bogeyUpDirection = getBogeyUpDirection(); boolean trackAxisAlongFirstCoordinate = isTrackAxisAlongFirstCoordinate(state); while (index != indexOf) { ResourceLocation id = bogeyCycle.get(index); Block newBlock = ForgeRegistries.BLOCKS.getValue(id); if (newBlock instanceof AbstractBogeyBlock bogey) { BlockState matchingBogey = bogey.getMatchingBogey(bogeyUpDirection, trackAxisAlongFirstCoordinate); if (matchingBogey != null) return copyProperties(state, matchingBogey); } index = (index + 1) % bogeyCycle.size(); } return state; } public BlockState getNextSize(Level level, BlockPos pos) { BlockEntity te = level.getBlockEntity(pos); if (te instanceof AbstractBogeyTileEntity sbte) return this.getNextSize(sbte); return level.getBlockState(pos); } /** * List of BlockState Properties to copy between sizes */ public List> propertiesToCopy() { return ImmutableList.of(WATERLOGGED, AXIS); } // generic method needed to satisfy Property and BlockState's generic requirements private > BlockState copyProperty(BlockState source, BlockState target, Property property) { if (source.hasProperty(property) && target.hasProperty(property)) { return target.setValue(property, source.getValue(property)); } return target; } private BlockState copyProperties(BlockState source, BlockState target) { for (Property property : propertiesToCopy()) target = copyProperty(source, target, property); return target; } public BlockState getNextSize(AbstractBogeyTileEntity sbte) { BogeySizes.BogeySize size = this.getSize(); BogeyStyle style = sbte.getStyle(); BlockState nextBlock = style.getNextBlock(size).defaultBlockState(); nextBlock = copyProperties(sbte.getBlockState(), nextBlock); return nextBlock; } public BlockState getStateOfSize(AbstractBogeyTileEntity sbte, BogeySizes.BogeySize size) { BogeyStyle style = sbte.getStyle(); BlockState state = style.getBlockOfSize(size).defaultBlockState(); return copyProperties(sbte.getBlockState(), state); } public BogeyStyle getNextStyle(Level level, BlockPos pos) { BlockEntity te = level.getBlockEntity(pos); if (te instanceof AbstractBogeyTileEntity sbte) return this.getNextStyle(sbte.getStyle()); return getDefaultStyle(); } public BogeyStyle getNextStyle(BogeyStyle style) { Collection allStyles = style.getCycleGroup().values(); if (allStyles.size() <= 1) return style; List list = new ArrayList<>(allStyles); return Iterate.cycleValue(list, style); } @Override public @NotNull BlockState rotate(@NotNull BlockState pState, Rotation pRotation) { return switch (pRotation) { case COUNTERCLOCKWISE_90, CLOCKWISE_90 -> pState.cycle(AXIS); default -> pState; }; } @Override public ItemRequirement getRequiredItems(BlockState state, BlockEntity te) { return new ItemRequirement(ItemRequirement.ItemUseType.CONSUME, AllBlocks.RAILWAY_CASING.asStack()); } public boolean canBeUpsideDown() { return false; } public boolean isUpsideDown(BlockState state) { return false; } public BlockState getVersion(BlockState base, boolean upsideDown) { return base; } }