From 0d110ca4f4e975a5d092e50093cad65efdec97c9 Mon Sep 17 00:00:00 2001 From: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> Date: Sun, 5 Mar 2023 20:21:56 -0800 Subject: [PATCH] Custom tool breakspeed by server; Closes #3348 Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> --- .../item/components/ToolBreakSpeedsUtils.java | 174 ------------------ .../CustomItemRegistryPopulator.java | 69 ++++--- .../inventory/item/CustomItemTranslator.java | 4 +- .../player/BedrockActionTranslator.java | 56 +++--- 4 files changed, 75 insertions(+), 228 deletions(-) delete mode 100644 core/src/main/java/org/geysermc/geyser/item/components/ToolBreakSpeedsUtils.java diff --git a/core/src/main/java/org/geysermc/geyser/item/components/ToolBreakSpeedsUtils.java b/core/src/main/java/org/geysermc/geyser/item/components/ToolBreakSpeedsUtils.java deleted file mode 100644 index 6330043e5..000000000 --- a/core/src/main/java/org/geysermc/geyser/item/components/ToolBreakSpeedsUtils.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.item.components; - -import com.nukkitx.nbt.NbtMap; -import com.nukkitx.nbt.NbtType; - -import java.util.ArrayList; -import java.util.List; - -public class ToolBreakSpeedsUtils { - public static int toolTierToSpeed(String toolTier) { - ToolTier tier = ToolTier.getByName(toolTier); - if (tier != null) { - return tier.getSpeed(); - } - - return 0; - } - - private static NbtMap createTagBreakSpeed(int speed, String... tags) { - StringBuilder builder = new StringBuilder("query.any_tag('"); - builder.append(tags[0]); - for (int i = 1; i < tags.length; i++) { - builder.append("', '").append(tags[i]); - } - builder.append("')"); - - return NbtMap.builder() - .putCompound("block", NbtMap.builder() - .putString("tags", builder.toString()) - .build()) - .putCompound("on_dig", NbtMap.builder() - .putCompound("condition", NbtMap.builder() - .putString("expression", "") - .putInt("version", -1) - .build()) - .putString("event", "tool_durability") - .putString("target", "self") - .build()) - .putInt("speed", speed) - .build(); - } - - private static NbtMap createBreakSpeed(int speed, String block) { - return NbtMap.builder() - .putCompound("block", NbtMap.builder() - .putString("name", block).build()) - .putCompound("on_dig", NbtMap.builder() - .putCompound("condition", NbtMap.builder() - .putString("expression", "") - .putInt("version", -1) - .build()) - .putString("event", "tool_durability") - .putString("target", "self") - .build()) - .putInt("speed", speed) - .build(); - } - - private static NbtMap createDigger(List speeds) { - return NbtMap.builder() - .putList("destroy_speeds", NbtType.COMPOUND, speeds) - .putCompound("on_dig", NbtMap.builder() - .putCompound("condition", NbtMap.builder() - .putString("expression", "") - .putInt("version", -1) - .build()) - .putString("event", "tool_durability") - .putString("target", "self") - .build()) - .putBoolean("use_efficiency", true) - .build(); - } - - public static NbtMap getAxeDigger(int speed) { - List speeds = new ArrayList<>(); - speeds.add(createTagBreakSpeed(speed, "wood", "pumpkin", "plant")); - - return createDigger(speeds); - } - - public static NbtMap getPickaxeDigger(int speed, String toolTier) { - List speeds = new ArrayList<>(); - if (toolTier.equals(ToolTier.DIAMOND.toString()) || toolTier.equals(ToolTier.NETHERITE.toString())) { - speeds.add(createTagBreakSpeed(speed, "iron_pick_diggable", "diamond_pick_diggable")); - } else { - speeds.add(createTagBreakSpeed(speed, "iron_pick_diggable")); - } - speeds.add(createTagBreakSpeed(speed, "stone", "metal", "rail", "mob_spawner")); - - return createDigger(speeds); - } - - public static NbtMap getShovelDigger(int speed) { - List speeds = new ArrayList<>(); - speeds.add(createTagBreakSpeed(speed, "dirt", "sand", "gravel", "grass", "snow")); - - return createDigger(speeds); - } - - public static NbtMap getSwordDigger(int speed) { - List speeds = new ArrayList<>(); - speeds.add(createBreakSpeed(speed, "minecraft:web")); - speeds.add(createBreakSpeed(speed, "minecraft:bamboo")); - - return createDigger(speeds); - } - - public static NbtMap getHoeDigger(int speed) { - List speeds = new ArrayList<>(); - speeds.add(createBreakSpeed(speed, "minecraft:leaves")); - speeds.add(createBreakSpeed(speed, "minecraft:leaves2")); - speeds.add(createBreakSpeed(speed, "minecraft:azalea_leaves")); - speeds.add(createBreakSpeed(speed, "minecraft:azalea_leaves_flowered")); - - speeds.add(createBreakSpeed(speed, "minecraft:sculk")); - speeds.add(createBreakSpeed(speed, "minecraft:sculk_catalyst")); - speeds.add(createBreakSpeed(speed, "minecraft:sculk_sensor")); - speeds.add(createBreakSpeed(speed, "minecraft:sculk_shrieker")); - speeds.add(createBreakSpeed(speed, "minecraft:sculk_vein")); - - speeds.add(createBreakSpeed(speed, "minecraft:nether_wart_block")); - speeds.add(createBreakSpeed(speed, "minecraft:warped_wart_block")); - - speeds.add(createBreakSpeed(speed, "minecraft:hay_block")); - speeds.add(createBreakSpeed(speed, "minecraft:moss_block")); - speeds.add(createBreakSpeed(speed, "minecraft:shroomlight")); - speeds.add(createBreakSpeed(speed, "minecraft:sponge")); - speeds.add(createBreakSpeed(speed, "minecraft:target")); - - return createDigger(speeds); - } - - public static NbtMap getShearsDigger(int speed) { - List speeds = new ArrayList<>(); - speeds.add(createBreakSpeed(speed, "minecraft:web")); - - speeds.add(createBreakSpeed(speed, "minecraft:leaves")); - speeds.add(createBreakSpeed(speed, "minecraft:leaves2")); - speeds.add(createBreakSpeed(speed, "minecraft:azalea_leaves")); - speeds.add(createBreakSpeed(speed, "minecraft:azalea_leaves_flowered")); - - speeds.add(createBreakSpeed(speed, "minecraft:wool")); - - speeds.add(createBreakSpeed(speed, "minecraft:glow_lichen")); - speeds.add(createBreakSpeed(speed, "minecraft:vine")); - - return createDigger(speeds); - } -} diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index e32030db6..9fed4b6d7 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -36,13 +36,14 @@ import org.geysermc.geyser.api.item.custom.CustomRenderOffsets; import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.item.GeyserCustomMappingData; -import org.geysermc.geyser.item.components.ToolBreakSpeedsUtils; import org.geysermc.geyser.item.components.WearableSlot; import org.geysermc.geyser.registry.type.GeyserMappingItem; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.NonVanillaItemRegistration; import javax.annotation.Nullable; + +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -226,34 +227,44 @@ public class CustomItemRegistryPopulator { boolean canDestroyInCreative = true; float miningSpeed = 1.0f; - if (toolType.equals("shears")) { - componentBuilder.putCompound("minecraft:digger", ToolBreakSpeedsUtils.getShearsDigger(15)); - } else { - int toolSpeed = ToolBreakSpeedsUtils.toolTierToSpeed(toolTier); - switch (toolType) { - case "sword" -> { - miningSpeed = 1.5f; - canDestroyInCreative = false; - componentBuilder.putCompound("minecraft:digger", ToolBreakSpeedsUtils.getSwordDigger(toolSpeed)); - componentBuilder.putCompound("minecraft:weapon", NbtMap.EMPTY); - } - case "pickaxe" -> { - componentBuilder.putCompound("minecraft:digger", ToolBreakSpeedsUtils.getPickaxeDigger(toolSpeed, toolTier)); - setItemTag(componentBuilder, "pickaxe"); - } - case "axe" -> { - componentBuilder.putCompound("minecraft:digger", ToolBreakSpeedsUtils.getAxeDigger(toolSpeed)); - setItemTag(componentBuilder, "axe"); - } - case "shovel" -> { - componentBuilder.putCompound("minecraft:digger", ToolBreakSpeedsUtils.getShovelDigger(toolSpeed)); - setItemTag(componentBuilder, "shovel"); - } - case "hoe" -> { - componentBuilder.putCompound("minecraft:digger", ToolBreakSpeedsUtils.getHoeDigger(toolSpeed)); - setItemTag(componentBuilder, "hoe"); - } - } + // This means client side the tool can never destroy a block + // This works because the molang '1' for tags will be true for all blocks and the speed will be 0 + // We want this since we calculate break speed server side in BedrockActionTranslator + List speed = new ArrayList<>(List.of( + NbtMap.builder() + .putCompound("block", NbtMap.builder() + .putString("tags", "1") + .build()) + .putCompound("on_dig", NbtMap.builder() + .putCompound("condition", NbtMap.builder() + .putString("expression", "") + .putInt("version", -1) + .build()) + .putString("event", "tool_durability") + .putString("target", "self") + .build()) + .putInt("speed", 0) + .build() + )); + componentBuilder.putCompound("minecraft:digger", + NbtMap.builder() + .putList("destroy_speeds", NbtType.COMPOUND, speed) + .putCompound("on_dig", NbtMap.builder() + .putCompound("condition", NbtMap.builder() + .putString("expression", "") + .putInt("version", -1) + .build()) + .putString("event", "tool_durability") + .putString("target", "self") + .build()) + .putBoolean("use_efficiency", true) + .build() + ); + + if (toolType.equals("sword")) { + miningSpeed = 1.5f; + canDestroyInCreative = false; + componentBuilder.putCompound("minecraft:weapon", NbtMap.EMPTY); } itemProperties.putBoolean("hand_equipped", true); diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/CustomItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/CustomItemTranslator.java index 82a8c9de1..bcbb97750 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/CustomItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/CustomItemTranslator.java @@ -39,9 +39,9 @@ import java.util.OptionalInt; /** * This is only a separate class for testing purposes so we don't have to load in GeyserImpl in ItemTranslator. */ -final class CustomItemTranslator { +public final class CustomItemTranslator { - static int getCustomItem(CompoundTag nbt, ItemMapping mapping) { + public static int getCustomItem(CompoundTag nbt, ItemMapping mapping) { if (nbt == null) { return -1; } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockActionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockActionTranslator.java index 28eaf9183..252311af5 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockActionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockActionTranslator.java @@ -42,9 +42,13 @@ import org.geysermc.geyser.api.block.custom.CustomBlockState; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.ItemFrameEntity; import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.registry.BlockRegistries; +import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.cache.SkullCache; +import org.geysermc.geyser.translator.inventory.item.CustomItemTranslator; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.util.BlockUtils; @@ -148,9 +152,16 @@ public class BedrockActionTranslator extends PacketTranslator -1 || (skull != null && skull.getCustomRuntimeId() != -1)) { session.setBlockBreakStartTime(System.currentTimeMillis()); } startBreak.setData((int) (65535 / breakTime)); @@ -197,27 +208,26 @@ public class BedrockActionTranslator extends PacketTranslator= breakTime * 50) { - // Play break sound and particle - LevelEventPacket effectPacket = new LevelEventPacket(); - effectPacket.setPosition(vectorFloat); - effectPacket.setType(LevelEventType.PARTICLE_DESTROY_BLOCK); - effectPacket.setData(session.getBlockMappings().getBedrockBlockId(breakingBlock)); - session.sendUpstreamPacket(effectPacket); - - // Break the block - ServerboundPlayerActionPacket finishBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.FINISH_DIGGING, - vector, Direction.VALUES[packet.getFace()], session.getWorldCache().nextPredictionSequence()); - session.sendDownstreamPacket(finishBreakingPacket); - session.setBlockBreakStartTime(0); - break; - } + + // If the block is custom, we must keep track of when it should break ourselves + long blockBreakStartTime = session.getBlockBreakStartTime(); + if (blockBreakStartTime != 0) { + long timeSinceStart = System.currentTimeMillis() - blockBreakStartTime; + // We need to add a slight delay to the break time, otherwise the client breaks blocks too fast + if (timeSinceStart >= (breakTime+=2) * 50) { + // Play break sound and particle + LevelEventPacket effectPacket = new LevelEventPacket(); + effectPacket.setPosition(vectorFloat); + effectPacket.setType(LevelEventType.PARTICLE_DESTROY_BLOCK); + effectPacket.setData(session.getBlockMappings().getBedrockBlockId(breakingBlock)); + session.sendUpstreamPacket(effectPacket); + + // Break the block + ServerboundPlayerActionPacket finishBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.FINISH_DIGGING, + vector, Direction.VALUES[packet.getFace()], session.getWorldCache().nextPredictionSequence()); + session.sendDownstreamPacket(finishBreakingPacket); + session.setBlockBreakStartTime(0); + break; } }