From fb5f0a933c64f2b7ee88ea1b4f47614843e91ec7 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 7 Jun 2023 18:53:34 +0200 Subject: [PATCH] [Feature] Support for Debug Items --- .../betterx/bclib/commands/PlaceCommand.java | 26 ++ .../commands/arguments/ConnectorArgument.java | 2 +- .../betterx/bclib/items/DebugDataItem.java | 284 ++++++++++++++++++ .../mixin/client/LevelRendererMixin.java | 85 ++++++ src/main/resources/bclib.accesswidener | 1 + src/main/resources/bclib.mixins.client.json | 1 + 6 files changed, 398 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/betterx/bclib/items/DebugDataItem.java create mode 100644 src/main/java/org/betterx/bclib/mixin/client/LevelRendererMixin.java diff --git a/src/main/java/org/betterx/bclib/commands/PlaceCommand.java b/src/main/java/org/betterx/bclib/commands/PlaceCommand.java index c30ea075..e99bd4c1 100644 --- a/src/main/java/org/betterx/bclib/commands/PlaceCommand.java +++ b/src/main/java/org/betterx/bclib/commands/PlaceCommand.java @@ -36,6 +36,7 @@ import net.minecraft.network.chat.Style; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.*; import net.minecraft.world.level.block.entity.*; @@ -603,6 +604,31 @@ public class PlaceCommand { return bb; } + public static BlockState setJigsawOrientation( + boolean rollable, + Player player, + BlockPos pos, + BlockState state + ) { + final int deltaY = player.getBlockY() - pos.getY(); + if (deltaY < 2 && deltaY > -2 && rollable) { + state = state.setValue( + JigsawBlock.ORIENTATION, + FrontAndTop.fromFrontAndTop(player.getDirection().getOpposite(), Direction.UP) + ); + } else if (deltaY < 0) { + state = state.setValue( + JigsawBlock.ORIENTATION, + FrontAndTop.fromFrontAndTop(Direction.DOWN, player.getDirection().getOpposite()) + ); + } else { + state = state.setValue( + JigsawBlock.ORIENTATION, + FrontAndTop.fromFrontAndTop(Direction.UP, player.getDirection().getOpposite()) + ); + } + return state; + } } /* diff --git a/src/main/java/org/betterx/bclib/commands/arguments/ConnectorArgument.java b/src/main/java/org/betterx/bclib/commands/arguments/ConnectorArgument.java index 2feffb4d..f7abf352 100644 --- a/src/main/java/org/betterx/bclib/commands/arguments/ConnectorArgument.java +++ b/src/main/java/org/betterx/bclib/commands/arguments/ConnectorArgument.java @@ -17,7 +17,7 @@ import java.util.concurrent.CompletableFuture; import org.jetbrains.annotations.NotNull; public class ConnectorArgument extends ResourceLocationArgument { - private static final Collection EXAMPLES = Arrays.asList("-:entrance", "-:decoration", "-:street"); + private static final Collection EXAMPLES = Arrays.asList("-:building_entrance", "-:bottom", "-:street"); @Override public Collection getExamples() { diff --git a/src/main/java/org/betterx/bclib/items/DebugDataItem.java b/src/main/java/org/betterx/bclib/items/DebugDataItem.java new file mode 100644 index 00000000..016886ca --- /dev/null +++ b/src/main/java/org/betterx/bclib/items/DebugDataItem.java @@ -0,0 +1,284 @@ +package org.betterx.bclib.items; + +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.commands.PlaceCommand; +import org.betterx.bclib.interfaces.ItemModelProvider; +import org.betterx.bclib.util.BlocksHelper; +import org.betterx.ui.ColorUtil; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.commands.arguments.blocks.BlockStateParser; +import net.minecraft.core.BlockPos; +import net.minecraft.core.FrontAndTop; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.data.worldgen.Pools; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.Mth; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.InteractionResultHolder; +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.UseOnContext; +import net.minecraft.world.level.ClipBlockStateContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.JigsawBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.JigsawBlockEntity; +import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec3; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +public class DebugDataItem extends Item implements ItemModelProvider { + + public static final ResourceLocation DEFAULT_ICON = new ResourceLocation("stick"); + + interface DebugInteraction { + InteractionResult use(UseOnContext useOnContext); + } + + interface DebugEntityInteraction extends DebugInteraction { + @Override + default InteractionResult use(UseOnContext useOnContext) { + var entity = useOnContext.getLevel().getBlockEntity(useOnContext.getClickedPos()); + if (entity != null) { + return use(useOnContext.getPlayer(), entity, useOnContext); + } + return InteractionResult.FAIL; + } + + InteractionResult use(Player player, BlockEntity entity, UseOnContext useOnContext); + } + + protected final DebugInteraction interaction; + protected final ResourceLocation icon; + public final boolean placeInAir; + + public DebugDataItem(DebugEntityInteraction interaction, boolean placeInAir, ResourceLocation icon) { + this((DebugInteraction) interaction, placeInAir, icon); + } + + public DebugDataItem(DebugInteraction interaction, boolean placeInAir, ResourceLocation icon) { + super(new Item.Properties().fireResistant().stacksTo(1)); + + this.interaction = interaction; + this.icon = (icon == null ? DEFAULT_ICON : icon); + this.placeInAir = placeInAir; + } + + @Override + public boolean isFoil(ItemStack itemStack) { + return true; + } + + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation resourceLocation) { + return ModelsHelper.createItemModel(icon); + } + + @Override + public InteractionResult useOn(UseOnContext useOnContext) { + if (!useOnContext.getPlayer().canUseGameMasterBlocks()) { + return InteractionResult.FAIL; + } + return interaction.use(useOnContext); + } + + public static void message(Player player, String text) { + message(player, text, ColorUtil.GRAY); + } + + public static void message(Player player, String text, int color) { + message(player, Component.literal(text).withStyle(Style.EMPTY.withColor(color))); + } + + public static void message(Player player, Component component) { + if (player instanceof ServerPlayer sp) { + sp.sendSystemMessage(component, true); + } + } + + @Override + public boolean canAttackBlock(BlockState blockState, Level level, BlockPos blockPos, Player player) { + return false; + } + + @Override + public InteractionResultHolder use(Level level, Player player, InteractionHand interactionHand) { + if (placeInAir) { + final var vec = new Vec3(0, 0, 1) + .xRot(-player.getXRot() * Mth.DEG_TO_RAD) + .yRot(-player.getYHeadRot() * Mth.DEG_TO_RAD); + + BlockHitResult hit = level.isBlockInLine(new ClipBlockStateContext( + player.getEyePosition(), + player.getEyePosition().add(vec.scale(6.0)), + BlockBehaviour.BlockStateBase::isAir + )); + + if (hit != null) { + var result = this.useOn(new UseOnContext(player, interactionHand, hit)); + + if (result == InteractionResult.SUCCESS) + return InteractionResultHolder.success(player.getItemInHand(interactionHand)); + else if (result == InteractionResult.FAIL) + return InteractionResultHolder.fail(player.getItemInHand(interactionHand)); + else if (result == InteractionResult.PASS) + return InteractionResultHolder.pass(player.getItemInHand(interactionHand)); + else if (result == InteractionResult.CONSUME) + return InteractionResultHolder.consume(player.getItemInHand(interactionHand)); + } + } + + return InteractionResultHolder.pass(player.getItemInHand(interactionHand)); + } + + public static DebugDataItem forLootTable(ResourceLocation table, Item icon) { + ResourceLocation iconId = BuiltInRegistries.ITEM.getKey(icon); + return new DebugDataItem( + (player, entity, ctx) -> { + CompoundTag tag = entity.saveWithoutMetadata(); + tag.remove(RandomizableContainerBlockEntity.LOOT_TABLE_SEED_TAG); + tag.remove("Items"); + + tag.putString(RandomizableContainerBlockEntity.LOOT_TABLE_TAG, table.toString()); + + entity.load(tag); + message(player, "Did set Loot Table to " + table.toString()); + return InteractionResult.SUCCESS; + }, + false, + iconId + ); + } + + public static DebugDataItem forSteetJigSaw( + String modID, + ResourceKey pool, + Item icon + ) { + return forJigsaw( + pool == null ? Pools.EMPTY : pool, + new ResourceLocation(modID, "street"), + JigsawBlockEntity.JointType.ALIGNED, + null, + null, + icon + ); + } + + public static DebugDataItem forHouseEntranceJigSaw( + String modID, + ResourceKey pool, + Item icon + ) { + return forJigsaw( + pool == null ? Pools.EMPTY : pool, + new ResourceLocation(modID, "building_entrance"), + JigsawBlockEntity.JointType.ALIGNED, + null, + null, + icon + ); + } + + public static DebugDataItem forDecorationJigSaw( + String modID, + ResourceKey pool, + Item icon + ) { + return forJigsaw( + pool == null ? Pools.EMPTY : pool, + new ResourceLocation(modID, "bottom"), + pool == null ? new ResourceLocation("empty") : new ResourceLocation(modID, "bottom"), + JigsawBlockEntity.JointType.ROLLABLE, + null, + pool == null ? FrontAndTop.NORTH_UP : null, + icon + ); + } + + public static DebugDataItem forJigsaw( + ResourceKey pool, + ResourceLocation connector, + JigsawBlockEntity.JointType type, + BlockState finalState, + FrontAndTop forceOrientation, + Item icon + ) { + return forJigsaw(pool, connector, connector, type, finalState, forceOrientation, icon); + } + + public static DebugDataItem forJigsaw( + ResourceKey pool, + ResourceLocation name, + ResourceLocation target, + JigsawBlockEntity.JointType type, + BlockState finalState, + FrontAndTop forceOrientation, + Item icon + ) { + ResourceLocation iconId = BuiltInRegistries.ITEM.getKey(icon); + return new DebugDataItem( + (ctx) -> { + final var player = ctx.getPlayer(); + final var level = ctx.getLevel(); + final var pos = ctx.getClickedPos(); + var state = level.getBlockState(pos); + var entity = level.getBlockEntity(pos); + var targetState = finalState; + if (!(entity instanceof JigsawBlockEntity)) { + if (targetState == null) + targetState = state; + state = Blocks.JIGSAW.defaultBlockState(); + level.setBlock(pos, state, BlocksHelper.SET_SILENT); + entity = level.getBlockEntity(pos); + + message(player, "Created JigSaw at " + pos.toString()); + } + + if (entity instanceof JigsawBlockEntity e) { + if (forceOrientation == null) { + state = PlaceCommand.setJigsawOrientation( + JigsawBlockEntity.JointType.ROLLABLE != type, + player, pos, state + ); + } else { + state = state.setValue(JigsawBlock.ORIENTATION, forceOrientation); + } + level.setBlock(pos, state, BlocksHelper.SET_SILENT); + + if (pool != null) e.setName(name); + if (pool != null) e.setTarget(target); + if (pool != null) e.setPool(pool); + if (targetState != null) e.setFinalState(BlockStateParser.serialize(targetState)); + e.setJoint(type); + + + message(player, "Did update Jigsaw at " + pos.toString()); + + return InteractionResult.SUCCESS; + } + return InteractionResult.FAIL; + }, + true, + iconId + ); + } + +} diff --git a/src/main/java/org/betterx/bclib/mixin/client/LevelRendererMixin.java b/src/main/java/org/betterx/bclib/mixin/client/LevelRendererMixin.java new file mode 100644 index 00000000..25bbf083 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/client/LevelRendererMixin.java @@ -0,0 +1,85 @@ +package org.betterx.bclib.mixin.client; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.items.DebugDataItem; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import net.minecraft.client.Camera; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.*; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.VoxelShape; + +import org.joml.Matrix4f; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(LevelRenderer.class) +public abstract class LevelRendererMixin { + @Final + @Shadow + private Minecraft minecraft; + @Shadow + @Final + private RenderBuffers renderBuffers; + + @Shadow + protected static void renderShape( + PoseStack poseStack, + VertexConsumer vertexConsumer, + VoxelShape voxelShape, + double d, + double e, + double f, + float g, + float h, + float i, + float j + ) { + } + + @Inject(method = "renderLevel", at = @At( + value = "INVOKE", + target = "Lcom/mojang/blaze3d/systems/RenderSystem;getModelViewStack()Lcom/mojang/blaze3d/vertex/PoseStack;", + shift = At.Shift.BEFORE + )) + public void bcl_renderLevel( + PoseStack poseStack, + float f, + long l, + boolean bl, + Camera camera, + GameRenderer gameRenderer, + LightTexture lightTexture, + Matrix4f matrix4f, + CallbackInfo info + ) { + if (BCLib.isDevEnvironment() && minecraft.hitResult instanceof BlockHitResult blockHitResult) { + //will render a block outline when empty blocks are targeted + ItemStack item = minecraft.player.getMainHandItem(); + if (item != null && (item.getItem() instanceof DebugDataItem ddi) && ddi.placeInAir) { + final var pos = blockHitResult.getBlockPos(); + final var state = Blocks.DIRT.defaultBlockState(); + MultiBufferSource.BufferSource bufferSource = this.renderBuffers.bufferSource(); + VertexConsumer consumer = bufferSource.getBuffer(RenderType.lines()); + Vec3 camPos = camera.getPosition(); + + this.renderShape( + poseStack, consumer, + state.getShape(minecraft.level, pos, CollisionContext.of(camera.getEntity())), + pos.getX() - camPos.x(), pos.getY() - camPos.y(), pos.getZ() - camPos.z(), + 246.0f / 0xff, 250.0f / 0xff, 112.0f / 0xff, 0.75F + ); + } + } + } +} diff --git a/src/main/resources/bclib.accesswidener b/src/main/resources/bclib.accesswidener index 00ffd25d..b4cdc639 100644 --- a/src/main/resources/bclib.accesswidener +++ b/src/main/resources/bclib.accesswidener @@ -34,6 +34,7 @@ accessible method net/minecraft/world/level/block/Blocks ocelotOrParrot (Lnet/mi accessible method net/minecraft/world/level/block/Blocks never (Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/BlockGetter;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/entity/EntityType;)Ljava/lang/Boolean; accessible method net/minecraft/world/level/block/Blocks never (Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/BlockGetter;Lnet/minecraft/core/BlockPos;)Z accessible method net/minecraft/world/level/levelgen/structure/pools/SinglePoolElement (Lcom/mojang/datafixers/util/Either;Lnet/minecraft/core/Holder;Lnet/minecraft/world/level/levelgen/structure/pools/StructureTemplatePool$Projection;)V +accessible method net/minecraft/world/level/levelgen/structure/pools/LegacySinglePoolElement (Lcom/mojang/datafixers/util/Either;Lnet/minecraft/core/Holder;Lnet/minecraft/world/level/levelgen/structure/pools/StructureTemplatePool$Projection;)V #Fields accessible field net/minecraft/world/entity/ai/village/poi/PoiTypes TYPE_BY_STATE Ljava/util/Map; diff --git a/src/main/resources/bclib.mixins.client.json b/src/main/resources/bclib.mixins.client.json index 1927ae61..8bd09e53 100644 --- a/src/main/resources/bclib.mixins.client.json +++ b/src/main/resources/bclib.mixins.client.json @@ -10,6 +10,7 @@ "ClientPacketListenerMixin", "ClientRecipeBookMixin", "FogRendererMixin", + "LevelRendererMixin", "MinecraftMixin", "ModelManagerMixin", "PresetEditorMixin",