diff --git a/crafting-dead-core/src/main/java/com/craftingdead/core/client/ClientDist.java b/crafting-dead-core/src/main/java/com/craftingdead/core/client/ClientDist.java index aab128b3..611430cf 100644 --- a/crafting-dead-core/src/main/java/com/craftingdead/core/client/ClientDist.java +++ b/crafting-dead-core/src/main/java/com/craftingdead/core/client/ClientDist.java @@ -20,6 +20,7 @@ import com.craftingdead.core.client.gui.screen.inventory.CraftingScreen; import com.craftingdead.core.network.message.play.DamageHandcuffsMessage; +import com.craftingdead.core.network.message.play.RightClickStateMessage; import com.craftingdead.core.network.message.play.TraumaPacket; import com.craftingdead.core.trauma.TraumaSeverity; import com.craftingdead.core.world.action.RemoveMagazineAction; @@ -198,6 +199,9 @@ public class ClientDist implements ModDist { private boolean wasSneaking; private long lastSneakPressTime; + public int rightClickTicks = 0; + public boolean wasRightClickDown = false; + private float lastPitch; private float lastYaw; private float lastRoll; @@ -492,6 +496,26 @@ public void handleClientTick(TickEvent.ClientTickEvent event) { return; } + if (this.minecraft.player == null || this.minecraft.getConnection() == null) { + return; + } + + boolean isRightClickDown = this.minecraft.options.keyUse.isDown(); + int ticks = this.minecraft.player.getUseItemRemainingTicks(); + + if (isRightClickDown || ticks > 0) { + if (!this.wasRightClickDown || ticks == 0) { + this.rightClickTicks = 0; + } + + this.rightClickTicks++; + } else { + this.rightClickTicks = 0; + } + this.wasRightClickDown = isRightClickDown; + NetworkChannel.PLAY.getSimpleChannel().sendToServer(new RightClickStateMessage( + isRightClickDown, this.rightClickTicks)); + var player = this.getPlayerExtension().orElse(null); if (player != null) { if (this.traumaAimTicks > 0) { diff --git a/crafting-dead-core/src/main/java/com/craftingdead/core/network/NetworkChannel.java b/crafting-dead-core/src/main/java/com/craftingdead/core/network/NetworkChannel.java index ce8e606f..18740c66 100644 --- a/crafting-dead-core/src/main/java/com/craftingdead/core/network/NetworkChannel.java +++ b/crafting-dead-core/src/main/java/com/craftingdead/core/network/NetworkChannel.java @@ -19,6 +19,7 @@ package com.craftingdead.core.network; import com.craftingdead.core.CraftingDead; +import com.craftingdead.core.network.message.play.BlockDestroyActionMessage; import com.craftingdead.core.network.message.play.CancelActionMessage; import com.craftingdead.core.network.message.play.CrouchMessage; import com.craftingdead.core.network.message.play.DamageHandcuffsMessage; @@ -30,6 +31,7 @@ import com.craftingdead.core.network.message.play.OpenStorageMessage; import com.craftingdead.core.network.message.play.ParachuteSyncMessage; import com.craftingdead.core.network.message.play.PerformActionMessage; +import com.craftingdead.core.network.message.play.RightClickStateMessage; import com.craftingdead.core.network.message.play.SecondaryActionMessage; import com.craftingdead.core.network.message.play.SetFireModeMessage; import com.craftingdead.core.network.message.play.SyncGunContainerSlotMessage; @@ -188,6 +190,20 @@ public void registerMessages(SimpleChannel simpleChannel) { .decoder(SyncProtectionConfigMessage::decode) .consumer(SyncProtectionConfigMessage::handle) .add(); + + simpleChannel + .messageBuilder(RightClickStateMessage.class, 0x15, NetworkDirection.PLAY_TO_SERVER) + .encoder(RightClickStateMessage::encode) + .decoder(RightClickStateMessage::decode) + .consumer(RightClickStateMessage::handle) + .add(); + + simpleChannel + .messageBuilder(BlockDestroyActionMessage.class, 0x16, NetworkDirection.PLAY_TO_SERVER) + .encoder(BlockDestroyActionMessage::encode) + .decoder(BlockDestroyActionMessage::decode) + .consumer(BlockDestroyActionMessage::handle) + .add(); } }; diff --git a/crafting-dead-core/src/main/java/com/craftingdead/core/network/message/play/BlockDestroyActionMessage.java b/crafting-dead-core/src/main/java/com/craftingdead/core/network/message/play/BlockDestroyActionMessage.java new file mode 100644 index 00000000..5a3aaee4 --- /dev/null +++ b/crafting-dead-core/src/main/java/com/craftingdead/core/network/message/play/BlockDestroyActionMessage.java @@ -0,0 +1,53 @@ +/* + * Crafting Dead + * Copyright (C) 2022 NexusNode LTD + * + * This Non-Commercial Software License Agreement (the "Agreement") is made between + * you (the "Licensee") and NEXUSNODE (BRAD HUNTER). (the "Licensor"). + * By installing or otherwise using Crafting Dead (the "Software"), you agree to be + * bound by the terms and conditions of this Agreement as may be revised from time + * to time at Licensor's sole discretion. + * + * If you do not agree to the terms and conditions of this Agreement do not download, + * copy, reproduce or otherwise use any of the source code available online at any time. + * + * https://github.com/nexusnode/crafting-dead/blob/1.18.x/LICENSE.txt + * + * https://craftingdead.net/terms.php + */ + +package com.craftingdead.core.network.message.play; + +import java.util.function.Supplier; +import net.minecraft.core.BlockPos; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraftforge.network.NetworkEvent; + +public record BlockDestroyActionMessage(BlockPos pos) { + + public static void encode(BlockDestroyActionMessage msg, FriendlyByteBuf buf) { + buf.writeBlockPos(msg.pos()); + } + + public static BlockDestroyActionMessage decode(FriendlyByteBuf buf) { + return new BlockDestroyActionMessage(buf.readBlockPos()); + } + + public static void handle(BlockDestroyActionMessage msg, Supplier ctx) { + ctx.get().enqueueWork(() -> { + var player = ctx.get().getSender(); + if (player == null) { + return; + } + var state = player.getLevel().getBlockState(msg.pos); + if (!state.isAir()) { + player.getLevel().levelEvent(2001, msg.pos, Block.getId(state)); + player.getLevel().setBlock(msg.pos, Blocks.AIR.defaultBlockState(), 3); + } + }); + ctx.get().setPacketHandled(true); + } +} + diff --git a/crafting-dead-core/src/main/java/com/craftingdead/core/network/message/play/RightClickStateMessage.java b/crafting-dead-core/src/main/java/com/craftingdead/core/network/message/play/RightClickStateMessage.java new file mode 100644 index 00000000..a167c415 --- /dev/null +++ b/crafting-dead-core/src/main/java/com/craftingdead/core/network/message/play/RightClickStateMessage.java @@ -0,0 +1,56 @@ +/* + * Crafting Dead + * Copyright (C) 2022 NexusNode LTD + * + * This Non-Commercial Software License Agreement (the "Agreement") is made between + * you (the "Licensee") and NEXUSNODE (BRAD HUNTER). (the "Licensor"). + * By installing or otherwise using Crafting Dead (the "Software"), you agree to be + * bound by the terms and conditions of this Agreement as may be revised from time + * to time at Licensor's sole discretion. + * + * If you do not agree to the terms and conditions of this Agreement do not download, + * copy, reproduce or otherwise use any of the source code available online at any time. + * + * https://github.com/nexusnode/crafting-dead/blob/1.18.x/LICENSE.txt + * + * https://craftingdead.net/terms.php + */ + +package com.craftingdead.core.network.message.play; + +import com.craftingdead.core.world.entity.extension.PlayerExtension; +import java.util.function.Supplier; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraftforge.network.NetworkEvent.Context; + +public record RightClickStateMessage(boolean isDown, int ticks) { + + public static void encode(RightClickStateMessage msg, FriendlyByteBuf buf) { + buf.writeBoolean(msg.isDown()); + buf.writeInt(msg.ticks()); + } + + public static RightClickStateMessage decode(FriendlyByteBuf buf) { + return new RightClickStateMessage(buf.readBoolean(), buf.readInt()); + } + + public static void handle(RightClickStateMessage msg, Supplier ctx) { + ctx.get().enqueueWork(() -> { + var player = ctx.get().getSender(); + if (player == null || !player.isAlive()) { + return; + } + var extension = PlayerExtension.getOrThrow(player); + if (msg.isDown()) { + extension.setHoldingRightClick(true); + int fixedTicks = msg.ticks() > 0 ? msg.ticks() : extension.getRightClickTicks(); + extension.setRightClickTicks(fixedTicks); + } else { + extension.setHoldingRightClick(false); + extension.setRightClickTicks(0); + } + }); + ctx.get().setPacketHandled(true); + } +} + diff --git a/crafting-dead-core/src/main/java/com/craftingdead/core/world/action/item/BlockItemAction.java b/crafting-dead-core/src/main/java/com/craftingdead/core/world/action/item/BlockItemAction.java index b7689c5b..1641ed5a 100644 --- a/crafting-dead-core/src/main/java/com/craftingdead/core/world/action/item/BlockItemAction.java +++ b/crafting-dead-core/src/main/java/com/craftingdead/core/world/action/item/BlockItemAction.java @@ -18,6 +18,8 @@ package com.craftingdead.core.world.action.item; +import com.craftingdead.core.network.NetworkChannel; +import com.craftingdead.core.network.message.play.BlockDestroyActionMessage; import com.craftingdead.core.world.action.ActionObserver; import com.craftingdead.core.world.action.ProgressBar; import com.craftingdead.core.world.entity.extension.LivingExtension; @@ -75,12 +77,18 @@ public boolean start(boolean simulate) { && this.type.getPredicate().test(this.blockState); } + @Override public boolean tick() { if (!this.performer.level().isClientSide() && !this.isWithinMaxDistance()) { this.performer.cancelAction(true); return false; } + // Makes sure it happens when the action is finished + // this.progress might not reach so >= 0.99F is the safer + if (this.progress >= 0.99F) { + this.destroyBlockAction(); + } return super.tick(); } @@ -98,4 +106,11 @@ private boolean isWithinMaxDistance() { public ItemActionType type() { return this.type; } + + public void destroyBlockAction() { + var pos = this.context.getClickedPos(); + if (this.type.getDestroyPredicate().test(this.performer.level().getBlockState(pos))) { + NetworkChannel.PLAY.getSimpleChannel().sendToServer(new BlockDestroyActionMessage(pos)); + } + } } diff --git a/crafting-dead-core/src/main/java/com/craftingdead/core/world/action/item/BlockItemActionType.java b/crafting-dead-core/src/main/java/com/craftingdead/core/world/action/item/BlockItemActionType.java index 53870a48..8a64c9e9 100644 --- a/crafting-dead-core/src/main/java/com/craftingdead/core/world/action/item/BlockItemActionType.java +++ b/crafting-dead-core/src/main/java/com/craftingdead/core/world/action/item/BlockItemActionType.java @@ -27,6 +27,7 @@ import net.minecraft.tags.TagKey; import net.minecraft.world.InteractionHand; import net.minecraft.world.item.context.UseOnContext; +import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.material.Fluid; import net.minecraft.world.level.material.FluidState; @@ -34,11 +35,13 @@ public class BlockItemActionType extends ItemActionType { private final Predicate predicate; + private final Predicate destroyPredicate; private final double maxDistanceSquared; protected BlockItemActionType(Builder builder) { super(builder); this.predicate = builder.predicate; + this.destroyPredicate = builder.destroyPredicate; this.maxDistanceSquared = builder.maxDistanceSquared; } @@ -46,6 +49,10 @@ public Predicate getPredicate() { return this.predicate; } + public Predicate getDestroyPredicate() { + return this.destroyPredicate; + } + public double getMaxDistanceSquared() { return this.maxDistanceSquared; } @@ -74,6 +81,7 @@ public static Builder builder() { public static final class Builder extends ItemActionType.Builder { private Predicate predicate; + private Predicate destroyPredicate; private double maxDistanceSquared = 4.0D; public Builder forBlock(Predicate predicate) { @@ -102,8 +110,34 @@ public Builder maxDistance(double maxDistance) { return this; } + public Builder destroyBlock(Block... blocks) { + Predicate blockPredicate = state -> { + for (Block block : blocks) { + if (state.is(block)) { + return true; + } + } + return false; + }; + if (this.destroyPredicate != null) { + this.destroyPredicate = this.destroyPredicate.and(blockPredicate); + } else { + this.destroyPredicate = blockPredicate; + } + if (this.predicate == null) { + this.predicate = this.destroyPredicate; + } + return this; + } + @Override public BlockItemActionType build() { + if (this.predicate == null) { + this.predicate = state -> false; + } + if (this.destroyPredicate == null) { + this.destroyPredicate = state -> false; + } return new BlockItemActionType(this); } } diff --git a/crafting-dead-core/src/main/java/com/craftingdead/core/world/action/item/ItemAction.java b/crafting-dead-core/src/main/java/com/craftingdead/core/world/action/item/ItemAction.java index 699e17f3..168d8ac6 100644 --- a/crafting-dead-core/src/main/java/com/craftingdead/core/world/action/item/ItemAction.java +++ b/crafting-dead-core/src/main/java/com/craftingdead/core/world/action/item/ItemAction.java @@ -18,6 +18,7 @@ package com.craftingdead.core.world.action.item; +import com.craftingdead.core.world.entity.extension.PlayerExtension; import java.util.Optional; import com.craftingdead.core.world.action.Action; import com.craftingdead.core.world.entity.extension.LivingExtension; @@ -30,6 +31,7 @@ public abstract class ItemAction implements Action { private final InteractionHand hand; private ItemStack originalStack; + public float progress; public ItemAction(InteractionHand hand) { this.hand = hand; @@ -67,6 +69,7 @@ public boolean start(boolean simulate) { @Override public void stop(StopReason reason) { + var extension = PlayerExtension.getOrThrow((Player) this.performer().entity()); this.performer().entity().stopUsingItem(); if (!reason.isCompleted()) { return; @@ -78,6 +81,14 @@ public void stop(StopReason reason) { .map(Item::getDefaultInstance) .orElse(ItemStack.EMPTY); + if (!this.shouldConsumeItem(this.performer()) + && this.type().getUsageDamage() >= 1) { + if (!heldStack.isEmpty() && heldStack.isDamageableItem()) { + heldStack.hurtAndBreak(this.type().getUsageDamage(), this.performer().entity(), + (p) -> p.broadcastBreakEvent(this.hand)); + } + } + if (this.shouldConsumeItem(this.performer())) { heldStack.shrink(1); } @@ -91,6 +102,7 @@ public void stop(StopReason reason) { } } + extension.setRightClickTicks(0); this.type().getFinishSound().ifPresent( sound -> this.performer().entity().playSound(sound, 1.0F, 1.0F)); } @@ -109,9 +121,11 @@ protected Optional getResultItem(LivingExtension performer) { @Override public boolean tick() { - if (!this.performer().entity().isUsingItem() + var extension = PlayerExtension.getOrThrow((Player) this.performer().entity()); + if (!extension.isHoldingRightClick() || this.originalStack != this.getItemStack()) { this.performer().cancelAction(true); + extension.setRightClickTicks(0); return false; } @@ -126,7 +140,8 @@ public float getProgress(float partialTicks) { if (!this.performer().entity().isUsingItem()) { return 0.0F; } else { - return (float) (this.performer().entity().getTicksUsingItem() + partialTicks) + this.progress = (this.performer().entity().getTicksUsingItem() + partialTicks) / this.type().getDurationTicks(); + return (this.performer().entity().getTicksUsingItem() + partialTicks) / this.type().getDurationTicks(); } } diff --git a/crafting-dead-core/src/main/java/com/craftingdead/core/world/action/item/ItemActionType.java b/crafting-dead-core/src/main/java/com/craftingdead/core/world/action/item/ItemActionType.java index 12f32100..4f2f867b 100644 --- a/crafting-dead-core/src/main/java/com/craftingdead/core/world/action/item/ItemActionType.java +++ b/crafting-dead-core/src/main/java/com/craftingdead/core/world/action/item/ItemActionType.java @@ -43,6 +43,7 @@ public abstract class ItemActionType private final int durationTicks; private final Predicate heldItemPredicate; private final boolean consumeItem; + private final int usageDamage; @Nullable private final Supplier resultItem; @Nullable @@ -56,6 +57,7 @@ protected ItemActionType(Builder builder) { this.durationTicks = builder.durationTicks; this.heldItemPredicate = builder.heldItemPredicate; this.consumeItem = builder.consumeItem; + this.usageDamage = builder.usageDamage; this.resultItem = builder.resultItem; this.finishSound = builder.finishSound; this.consumeItemInCreative = builder.consumeItemInCreative; @@ -78,6 +80,10 @@ public boolean shouldConsumeItem() { return this.consumeItem; } + public int getUsageDamage() { + return this.usageDamage; + } + public Optional getResultItem() { return Optional.ofNullable(this.resultItem).map(Supplier::get); } @@ -121,6 +127,7 @@ public static abstract class Builder> { private Predicate heldItemPredicate = Predicates.alwaysTrue(); private boolean consumeItem = true; + private int usageDamage = 0; @Nullable private Supplier resultItem; @Nullable @@ -163,6 +170,11 @@ public SELF consumeItem(boolean consumeItem) { return this.self(); } + public SELF usageDamage(int usageDamage) { + this.usageDamage = usageDamage; + return this.self(); + } + public SELF resultItem(Supplier resultItem) { this.resultItem = resultItem; return this.self(); diff --git a/crafting-dead-core/src/main/java/com/craftingdead/core/world/entity/extension/PlayerExtension.java b/crafting-dead-core/src/main/java/com/craftingdead/core/world/entity/extension/PlayerExtension.java index 35510e15..ac3013d1 100644 --- a/crafting-dead-core/src/main/java/com/craftingdead/core/world/entity/extension/PlayerExtension.java +++ b/crafting-dead-core/src/main/java/com/craftingdead/core/world/entity/extension/PlayerExtension.java @@ -44,6 +44,14 @@ static

PlayerExtension

get(P player) { return CapabilityUtil.get(LivingExtension.CAPABILITY, player, PlayerExtension.class); } + boolean isHoldingRightClick(); + + int getRightClickTicks(); + + void setHoldingRightClick(boolean value); + + void setRightClickTicks(int ticks); + boolean isCombatModeEnabled(); void setCombatModeEnabled(boolean combatModeEnabled); diff --git a/crafting-dead-core/src/main/java/com/craftingdead/core/world/entity/extension/PlayerExtensionImpl.java b/crafting-dead-core/src/main/java/com/craftingdead/core/world/entity/extension/PlayerExtensionImpl.java index b2ba3834..d18961ad 100644 --- a/crafting-dead-core/src/main/java/com/craftingdead/core/world/entity/extension/PlayerExtensionImpl.java +++ b/crafting-dead-core/src/main/java/com/craftingdead/core/world/entity/extension/PlayerExtensionImpl.java @@ -65,12 +65,20 @@ final class PlayerExtensionImpl private static final EntityDataAccessor HANDCUFFS = new EntityDataAccessor<>(0x01, EntityDataSerializers.ITEM_STACK); + private static final EntityDataAccessor HOLDING_RIGHT_CLICK = + new EntityDataAccessor<>(0x02, EntityDataSerializers.BOOLEAN); + + private static final EntityDataAccessor RIGHT_CLICK_TICKS = + new EntityDataAccessor<>(0x03, EntityDataSerializers.INT); + private boolean cachedCombatModeEnabled; PlayerExtensionImpl(E entity) { super(entity); this.data.register(COMBAT_MODE_ENABLED, false); this.data.register(HANDCUFFS, ItemStack.EMPTY); + this.data.register(HOLDING_RIGHT_CLICK, false); + this.data.register(RIGHT_CLICK_TICKS, 0); } @Override @@ -162,6 +170,26 @@ public boolean handleBlockBreak(BlockPos pos, BlockState block, MutableInt xp) { return this.handlers.values().stream().anyMatch(e -> e.handleBlockBreak(pos, block, xp)); } + @Override + public boolean isHoldingRightClick() { + return this.data.get(HOLDING_RIGHT_CLICK); + } + + @Override + public int getRightClickTicks() { + return this.data.get(RIGHT_CLICK_TICKS); + } + + @Override + public void setHoldingRightClick(boolean value) { + this.data.set(HOLDING_RIGHT_CLICK, value); + } + + @Override + public void setRightClickTicks(int ticks) { + this.data.set(RIGHT_CLICK_TICKS, ticks); + } + @Override public boolean isCombatModeEnabled() { return !this.entity().isSpectator()