diff --git a/.github/scripts/.gitignore b/.github/scripts/.gitignore new file mode 100644 index 0000000..aa921ba --- /dev/null +++ b/.github/scripts/.gitignore @@ -0,0 +1,10 @@ +.cache/ +BuildData/ +Bukkit/ +CraftBukkit/ +Spigot/ +work/ +*.txt +*.jar +apache-maven-3.9.6/ + diff --git a/.github/scripts/build_remapped_jars.py b/.github/scripts/build_remapped_jars.py index d0b99c6..2673740 100644 --- a/.github/scripts/build_remapped_jars.py +++ b/.github/scripts/build_remapped_jars.py @@ -3,7 +3,30 @@ You'll need these versions to compile the server jars: # Java 8: 1.9 -> 1.16 # Java 16: 1.17 -# Java 17: 1.18+ +# Java 17: 1.18 - 1.20.4 +# Java 21: 1.20.5 - 1.21.1 +==> https://hub.spigotmc.org/versions/1.21.1.json + +Java Versions: + public static final JavaVersion JAVA_5 = new JavaVersion( "Java 5", 49 ); + public static final JavaVersion JAVA_6 = new JavaVersion( "Java 6", 50 ); + public static final JavaVersion JAVA_7 = new JavaVersion( "Java 7", 51 ); + public static final JavaVersion JAVA_8 = new JavaVersion( "Java 8", 52 ); + public static final JavaVersion JAVA_9 = new JavaVersion( "Java 9", 53 ); + public static final JavaVersion JAVA_10 = new JavaVersion( "Java 10", 54 ); + public static final JavaVersion JAVA_11 = new JavaVersion( "Java 11", 55 ); + public static final JavaVersion JAVA_12 = new JavaVersion( "Java 12", 56 ); + public static final JavaVersion JAVA_13 = new JavaVersion( "Java 13", 57 ); + public static final JavaVersion JAVA_14 = new JavaVersion( "Java 14", 58 ); + public static final JavaVersion JAVA_15 = new JavaVersion( "Java 15", 59 ); + public static final JavaVersion JAVA_16 = new JavaVersion( "Java 16", 60 ); + public static final JavaVersion JAVA_17 = new JavaVersion( "Java 17", 61 ); + public static final JavaVersion JAVA_18 = new JavaVersion( "Java 18", 62 ); + public static final JavaVersion JAVA_19 = new JavaVersion( "Java 19", 63 ); + public static final JavaVersion JAVA_20 = new JavaVersion( "Java 20", 64 ); + public static final JavaVersion JAVA_21 = new JavaVersion( "Java 21", 65 ); + public static final JavaVersion JAVA_22 = new JavaVersion( "Java 22", 66 ); + """ import logging import os @@ -18,18 +41,55 @@ logger = logging.getLogger(__name__) java_versions_needed = { - '9': 8, - '10': 8, - '11': 8, - '12': 8, - '13': 8, - '14': 8, - '15': 8, - '16': 8, - '17': 16, - '18': 17, - '19': 17, - '20': 17, + "1.8": (7, 8), + "1.8.3": (7, 8), + "1.8.4": (7, 8), + "1.8.5": (7, 8), + "1.8.6": (7, 8), + "1.8.7": (7, 8), + "1.8.8": (7, 8), + "1.9": (7, 8), + "1.9.2": (7, 8), + "1.9.4": (7, 8), + "1.10": (7, 8), + "1.10.2": (7, 8), + "1.11": (7, 8), + "1.11.1": (7, 8), + "1.11.2": (7, 8), + "1.12": (8, 8), + "1.12.1": (8, 9), + "1.12.2": (8, 10), + "1.13": (8, 11), + "1.13.1": (8, 11), + "1.13.2": (8, 12), + "1.14": (8, 12), + "1.14.1": (8, 12), + "1.14.2": (8, 12), + "1.14.3": (8, 12), + "1.14.4": (8, 13), + "1.15": (8, 13), + "1.15.1": (8, 13), + "1.15.2": (8, 14), + "1.16.1": (8, 14), + "1.16.2": (8, 14), + "1.16.3": (8, 15), + "1.16.4": (8, 15), + "1.16.5": (8, 16), + "1.17": (16, 16), + "1.17.1": (16, 17), + "1.18": (17, 17), + "1.18.1": (17, 17), + "1.18.2": (17, 18), + "1.19": (17, 18), + "1.19.1": (17, 18), + "1.19.2": (17, 19), + "1.19.3": (17, 20), + "1.19.4": (17, 20), + "1.20": (17, 21), + "1.20.1": (17, 21), + "1.20.2": (17, 21), + "1.20.4": (17, 22), + "1.20.6": (21, 22), } __dir__ = Path(__file__).parent.resolve().absolute() __root__ = __dir__.parent.parent @@ -42,14 +102,27 @@ def find_version_to_build(directory: Path) -> tuple[str, str]: def get_java_path(version: str) -> str: - minor = version.split('.')[1] - java_version = java_versions_needed[minor] + versions = version.split('.') + min_java, max_java = -1, -1 + + for version_needed in [version, ".".join(versions[:3]), ".".join(versions[:2]), versions[1]]: + if version_needed not in java_versions_needed: + continue + + min_java, max_java = java_versions_needed[version_needed] + + for java_version in range(min_java, max_java + 1): + java_path = os.getenv(f'JAVA_HOME_{java_version}_X64') + if java_path: + return java_path + '/bin/java' + + alternative = Path(f"/usr/lib/jvm/java-{java_version}-openjdk-amd64/bin/java") + if alternative.exists(): + return str(alternative) - java_path = os.getenv(f'JAVA_HOME_{java_version}_X64') - if not java_path: - raise Exception(f"No JAVA_HOME found for version {version}") + break # First one should stop the loop for searching! - return java_path + '/bin/java' + raise Exception(f"No JAVA_HOME found for version {version}, I need something between '{min_java}' and '{max_java}'") def run_build_tools(version: str, spigot_needed: str) -> None: diff --git a/.github/scripts/fetch_version.py b/.github/scripts/fetch_version.py new file mode 100644 index 0000000..f955f77 --- /dev/null +++ b/.github/scripts/fetch_version.py @@ -0,0 +1,138 @@ +""" +Steps: +- goto: https://hub.spigotmc.org/versions/ +- copy/paste the list in the "txt" variable underneath. +- run the script +- copy/paste the output in the "build_remapped_jars.py" script +""" +import json +import re +from pathlib import Path +from pprint import pprint + +import requests + +version_url = "https://hub.spigotmc.org/versions/" + +txt = """ +1.10.2.json 10-Dec-2021 01:54 325 +1.10.json 10-Dec-2021 01:54 325 +1.11.1.json 10-Dec-2021 02:02 327 +1.11.2.json 10-Dec-2021 02:02 327 +1.11.json 19-Dec-2016 12:14 325 +1.12.1.json 29-Aug-2018 23:54 352 +1.12.2.json 10-Dec-2021 01:31 354 +1.12.json 29-Aug-2018 23:56 352 +1.13-pre7.json 21-Jul-2018 04:58 325 +1.13.1.json 22-Oct-2018 19:00 352 +1.13.2.json 10-Dec-2021 01:31 354 +1.13.json 29-Aug-2018 23:54 352 +1.14-pre5.json 25-Apr-2019 00:20 352 +1.14.1.json 27-May-2019 02:27 353 +1.14.2.json 21-Jun-2019 09:57 353 +1.14.3-pre4.json 23-Jun-2019 01:05 353 +1.14.3.json 19-Jul-2019 23:04 353 +1.14.4.json 10-Dec-2021 01:31 355 +1.14.json 13-May-2019 22:53 353 +1.15.1.json 21-Jan-2020 03:40 353 +1.15.2.json 10-Dec-2021 01:31 355 +1.15.json 17-Dec-2019 21:19 353 +1.16.1.json 11-Aug-2020 07:40 353 +1.16.2.json 10-Sep-2020 20:55 353 +1.16.3.json 25-Oct-2020 07:17 353 +1.16.4.json 14-Jan-2021 22:05 353 +1.16.5.json 10-Dec-2021 01:31 355 +1.17.1.json 10-Dec-2021 01:32 355 +1.17.json 06-Jul-2021 12:21 353 +1.18-pre5.json 23-Nov-2021 22:37 353 +1.18-pre8.json 25-Nov-2021 23:20 353 +1.18-rc3.json 29-Nov-2021 05:03 353 +1.18.1.json 28-Feb-2022 15:04 353 +1.18.2.json 07-Jun-2022 16:08 353 +1.18.json 10-Dec-2021 13:16 353 +1.19.1.json 04-Aug-2022 10:41 353 +1.19.2.json 07-Dec-2022 16:12 353 +1.19.3.json 14-Mar-2023 16:36 353 +1.19.4.json 07-Jun-2023 15:58 353 +1.19.json 25-Jul-2022 09:01 353 +1.20.1.json 21-Sep-2023 16:40 353 +1.20.2.json 05-Dec-2023 16:43 353 +1.20.3.json 23-Apr-2024 15:25 353 +1.20.4.json 23-Apr-2024 15:25 353 +1.20.5.json 28-May-2024 21:03 353 +1.20.6.json 28-May-2024 21:03 353 +1.20.json 21-Sep-2023 16:40 353 +1.8.3.json 17-May-2015 09:44 302 +1.8.4.json 10-Dec-2021 01:54 315 +1.8.5.json 10-Dec-2021 01:54 315 +1.8.6.json 10-Dec-2021 01:54 315 +1.8.7.json 10-Dec-2021 01:54 315 +1.8.8.json 10-Dec-2021 01:54 315 +1.8.json 16-Apr-2015 22:00 479 +1.9.2.json 07-May-2016 06:41 323 +1.9.4.json 10-Dec-2021 01:54 336 +1.9.json 30-Mar-2016 16:03 323 +""" + + +def natural_sort_key(s): + return [int(text) if text.isdigit() else text.lower() for text in re.split('([0-9]+)', s)] + + +def _retrieve_versions() -> dict: + versions = {} + + for line in txt.strip().splitlines(): + fields = line.split() + json_file = fields[0] + size = int(fields[-1]) + + if '-' in json_file: # -pre, -rc, ... -> Not interested! + continue + + target = Path(__file__).parent / '.cache' / json_file + if not target.exists() or target.stat().st_size != size: + print(" > Working on:", json_file) + target.parent.mkdir(exist_ok=True, parents=True) + response = requests.get(version_url + json_file) + target.write_bytes(response.content) + data = response.json() + else: + data = json.loads(target.read_bytes()) + + versions[target.stem] = data + + return versions + + +def _convert_java_class_version_to_java_version(nr1, nr2) -> tuple[int, int]: + version_table = { + 49: 5, + 50: 6, + 51: 7, + 52: 8, + 53: 9, + 54: 10, + 55: 11, + 56: 12, + 57: 13, + 58: 14, + 59: 15, + 60: 16, + 61: 17, + 62: 18, + 63: 19, + 64: 20, + 65: 21, + 66: 22, + } + + return version_table[nr1], version_table[nr2] + + +if __name__ == '__main__': + versions = _retrieve_versions() + for version in sorted(versions, key=natural_sort_key): + data = versions[version] + min_max = data.get('javaVersions', [51, 52]) + print(f'"{version}": {_convert_java_class_version_to_java_version(*min_max)},') diff --git a/.github/workflows/build_cache.yml b/.github/workflows/build_cache.yml index a1fcd87..4607f7e 100644 --- a/.github/workflows/build_cache.yml +++ b/.github/workflows/build_cache.yml @@ -11,6 +11,10 @@ jobs: build: runs-on: ubuntu-latest + permissions: + contents: write + packages: write + steps: - uses: actions/checkout@v4 @@ -46,6 +50,7 @@ jobs: 8 16 17 + 21 - name: Set up Python uses: actions/setup-python@v5 diff --git a/.github/workflows/maven-build.yml b/.github/workflows/maven-build.yml index 0c3718b..b972652 100644 --- a/.github/workflows/maven-build.yml +++ b/.github/workflows/maven-build.yml @@ -33,6 +33,7 @@ jobs: 8 16 17 + 21 - name: Set up Python uses: actions/setup-python@v5 diff --git a/.github/workflows/maven-release.yml b/.github/workflows/maven-release.yml index a79681e..7157a10 100644 --- a/.github/workflows/maven-release.yml +++ b/.github/workflows/maven-release.yml @@ -50,6 +50,7 @@ jobs: 8 16 17 + 21 - name: Set up Python uses: actions/setup-python@v5 diff --git a/.gitignore b/.gitignore index fb88430..363bc06 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,12 @@ tmp/ *.iws log*.txt *.log + +# Buildtools related stuff BuildTools.log.txt +.cache/ +apache-maven-*/ +BuildData/ +CraftBukkit/ +Spigot/ +spigot-*.jar diff --git a/pom.xml b/pom.xml index e774dca..20a4356 100644 --- a/pom.xml +++ b/pom.xml @@ -30,6 +30,8 @@ core dist + v1_21 + v1_20_R6 v1_20_R4 v1_20_R2 v1_20_R1 diff --git a/v1_20_R6/pom.xml b/v1_20_R6/pom.xml new file mode 100644 index 0000000..292cf6c --- /dev/null +++ b/v1_20_R6/pom.xml @@ -0,0 +1,75 @@ + + + 4.0.0 + dualwield-v1_20_R6 + + + 1.20.6-R0.1-SNAPSHOT + 1.8 + UTF-8 + + + + org.avarion + dualwield-parent + ${revision} + + + + + org.spigotmc + spigot + ${spigot.version} + remapped-mojang + provided + + + org.avarion + dualwield-core + ${project.parent.version} + + + + + + + net.md-5 + specialsource-maven-plugin + 1.2.2 + + + package + + remap + + remap-obf + + org.spigotmc:minecraft-server:${spigot.version}:txt:maps-mojang + true + org.spigotmc:spigot:${spigot.version}:jar:remapped-mojang + + true + remapped-obf + + + + package + + remap + + remap-spigot + + + ${project.build.directory}/${project.artifactId}-${project.version}-remapped-obf.jar + + org.spigotmc:minecraft-server:${spigot.version}:csrg:maps-spigot + org.spigotmc:spigot:${spigot.version}:jar:remapped-obf + + + + + + + + \ No newline at end of file diff --git a/v1_20_R6/src/main/java/org/avarion/dualwield/nms/NMS_v1_20_R6.java b/v1_20_R6/src/main/java/org/avarion/dualwield/nms/NMS_v1_20_R6.java new file mode 100644 index 0000000..271b488 --- /dev/null +++ b/v1_20_R6/src/main/java/org/avarion/dualwield/nms/NMS_v1_20_R6.java @@ -0,0 +1,156 @@ +package org.avarion.dualwield.nms; + +import com.google.common.collect.Iterables; +import com.google.common.collect.Multimap; +import net.minecraft.core.BlockPos; +import net.minecraft.network.protocol.game.ClientboundBlockDestructionPacket; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.ai.attributes.Attribute; +import net.minecraft.world.entity.ai.attributes.AttributeInstance; +import net.minecraft.world.entity.ai.attributes.AttributeModifier; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.SoundType; +import org.bukkit.Sound; +import org.bukkit.craftbukkit.v1_20_R4.CraftWorld; // Correct R4! 1.20.6 has R4 as API version!! +import org.bukkit.craftbukkit.v1_20_R4.entity.CraftEntity; // Correct R4! 1.20.6 has R4 as API version!! +import org.bukkit.craftbukkit.v1_20_R4.entity.CraftPlayer; // Correct R4! 1.20.6 has R4 as API version!! +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack; // Correct R4! 1.20.6 has R4 as API version!! +import org.bukkit.entity.Player; + +import java.util.UUID; + +public final class NMS_v1_20_R6 implements NMS { + @Override + public void handAnimation(Player player, org.bukkit.inventory.EquipmentSlot equipmentSlot) { + switch (equipmentSlot) { + case HAND: { + player.swingMainHand(); + } + + case OFF_HAND: { + player.swingOffHand(); + } + } + } + + @Override + public void blockBreakAnimation(Player player, org.bukkit.block.Block block, int animationID, int stage) { + ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); + ServerGamePacketListenerImpl serverGamePacketListener = serverPlayer.connection; + BlockPos blockPosition = new BlockPos(block.getX(), block.getY(), block.getZ()); + + serverGamePacketListener.send(new ClientboundBlockDestructionPacket(animationID, blockPosition, stage)); + } + + @Override + public void blockCrackParticle(org.bukkit.block.Block block) { + block.getWorld().spawnParticle(org.bukkit.Particle.BLOCK_CRACK, block.getLocation().add(0.5, 0, 0.5), + 10, block.getBlockData()); + } + + @Override + public float getToolStrength(org.bukkit.block.Block block, org.bukkit.inventory.ItemStack itemStack) { + if (itemStack.getAmount() != 0) { + ItemStack craftItemStack = CraftItemStack.asNMSCopy(itemStack); + ServerLevel serverLevel = ((CraftWorld) block.getWorld()).getHandle(); + Block nmsBlock = serverLevel.getBlockState(new BlockPos(block.getX(), block.getY(), block.getZ())).getBlock(); + + return craftItemStack.getDestroySpeed(nmsBlock.defaultBlockState()); + } + + return 1; + } + + @Override + public double getAttackDamage(org.bukkit.inventory.ItemStack itemStack) { + return getItemStackAttribute(itemStack, Attributes.ATTACK_DAMAGE); + } + + @Override + public double getAttackSpeed(org.bukkit.inventory.ItemStack itemStack) { + return getItemStackAttribute(itemStack, Attributes.ATTACK_SPEED); + } + + private double getItemStackAttribute(org.bukkit.inventory.ItemStack itemStack, Attribute attribute) { + if (itemStack.getAmount() != 0) { + ItemStack craftItemStack = CraftItemStack.asNMSCopy(itemStack); + Multimap attributeMultimap = craftItemStack + .getAttributeModifiers(EquipmentSlot.MAINHAND); + AttributeModifier attributeModifier = Iterables.getFirst(attributeMultimap.get(attribute), null); + + return attributeModifier != null ? attributeModifier.amount : 0; + } + + return 0; + } + + @Override + public Sound getHitSound(org.bukkit.block.Block block) { + try { + ServerLevel serverLevel = ((CraftWorld) block.getWorld()).getHandle(); + Block nmsBlock = serverLevel.getBlockState(new BlockPos(block.getX(), block.getY(), block.getZ())).getBlock(); + SoundType soundType = nmsBlock.getSoundType(nmsBlock.defaultBlockState()); + + return Sound.valueOf(soundType.getHitSound().getLocation().getPath().toUpperCase() + .replace(".", "_")); + } catch (IllegalArgumentException ignored) { + } + + return Sound.BLOCK_STONE_HIT; + } + + @Override + public float getBlockHardness(org.bukkit.block.Block block) { + ServerLevel serverLevel = ((CraftWorld) block.getWorld()).getHandle(); + Block nmsBlock = serverLevel.getBlockState(new BlockPos(block.getX(), block.getY(), block.getZ())).getBlock(); + + return nmsBlock.defaultBlockState().getDestroySpeed(null, null); + } + + @Override + public boolean breakBlock(Player player, org.bukkit.block.Block block) { + return player.breakBlock(block); + } + + @Override + public void setModifier(Player player, double damage, double speed, UUID uuid) { + ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); + AttributeInstance damageAttributeInstance = serverPlayer.getAttribute(Attributes.ATTACK_DAMAGE); + AttributeInstance speedAttributeInstance = serverPlayer.getAttribute(Attributes.ATTACK_SPEED); + + if (damageAttributeInstance != null) { + damageAttributeInstance.addTransientModifier(new AttributeModifier(uuid, "Weapon modifier", damage, + AttributeModifier.Operation.ADDITION)); + } + + if (speedAttributeInstance != null) { + speedAttributeInstance.addTransientModifier(new AttributeModifier(uuid, "Weapon modifier", speed, + AttributeModifier.Operation.ADDITION)); + } + } + + @Override + public void removeModifier(Player player, UUID uuid) { + ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); + AttributeInstance damageAttributeInstance = serverPlayer.getAttribute(Attributes.ATTACK_DAMAGE); + AttributeInstance speedAttributeInstance = serverPlayer.getAttribute(Attributes.ATTACK_SPEED); + + if (damageAttributeInstance != null) { + damageAttributeInstance.removeModifier(uuid); + } + + if (speedAttributeInstance != null) { + speedAttributeInstance.removeModifier(uuid); + } + } + + @Override + public void attack(Player player, org.bukkit.entity.Entity entity) { + (((CraftPlayer) player).getHandle()).attack(((CraftEntity) entity).getHandle()); + } +} \ No newline at end of file diff --git a/v1_21/pom.xml b/v1_21/pom.xml new file mode 100644 index 0000000..da91501 --- /dev/null +++ b/v1_21/pom.xml @@ -0,0 +1,75 @@ + + + 4.0.0 + dualwield-v1_21_R1 + + + 1.21-R0.1-SNAPSHOT + 1.8 + UTF-8 + + + + org.avarion + dualwield-parent + ${revision} + + + + + org.spigotmc + spigot + ${spigot.version} + remapped-mojang + provided + + + org.avarion + dualwield-core + ${project.parent.version} + + + + + + + net.md-5 + specialsource-maven-plugin + 1.2.2 + + + package + + remap + + remap-obf + + org.spigotmc:minecraft-server:${spigot.version}:txt:maps-mojang + true + org.spigotmc:spigot:${spigot.version}:jar:remapped-mojang + + true + remapped-obf + + + + package + + remap + + remap-spigot + + + ${project.build.directory}/${project.artifactId}-${project.version}-remapped-obf.jar + + org.spigotmc:minecraft-server:${spigot.version}:csrg:maps-spigot + org.spigotmc:spigot:${spigot.version}:jar:remapped-obf + + + + + + + + \ No newline at end of file diff --git a/v1_21/src/main/java/org/avarion/dualwield/nms/NMS_v1_21_R1.java b/v1_21/src/main/java/org/avarion/dualwield/nms/NMS_v1_21_R1.java new file mode 100644 index 0000000..bc689c3 --- /dev/null +++ b/v1_21/src/main/java/org/avarion/dualwield/nms/NMS_v1_21_R1.java @@ -0,0 +1,156 @@ +package org.avarion.dualwield.nms; + +import com.google.common.collect.Iterables; +import com.google.common.collect.Multimap; +import net.minecraft.core.BlockPos; +import net.minecraft.network.protocol.game.ClientboundBlockDestructionPacket; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.ai.attributes.Attribute; +import net.minecraft.world.entity.ai.attributes.AttributeInstance; +import net.minecraft.world.entity.ai.attributes.AttributeModifier; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.SoundType; +import org.bukkit.Sound; +import org.bukkit.craftbukkit.v1_21_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_21_R1.entity.CraftEntity; +import org.bukkit.craftbukkit.v1_21_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftItemStack; +import org.bukkit.entity.Player; + +import java.util.UUID; + +public final class NMS_v1_21_R1 implements NMS { + @Override + public void handAnimation(Player player, org.bukkit.inventory.EquipmentSlot equipmentSlot) { + switch (equipmentSlot) { + case HAND: { + player.swingMainHand(); + } + + case OFF_HAND: { + player.swingOffHand(); + } + } + } + + @Override + public void blockBreakAnimation(Player player, org.bukkit.block.Block block, int animationID, int stage) { + ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); + ServerGamePacketListenerImpl serverGamePacketListener = serverPlayer.connection; + BlockPos blockPosition = new BlockPos(block.getX(), block.getY(), block.getZ()); + + serverGamePacketListener.send(new ClientboundBlockDestructionPacket(animationID, blockPosition, stage)); + } + + @Override + public void blockCrackParticle(org.bukkit.block.Block block) { + block.getWorld().spawnParticle(org.bukkit.Particle.BLOCK_CRACK, block.getLocation().add(0.5, 0, 0.5), + 10, block.getBlockData()); + } + + @Override + public float getToolStrength(org.bukkit.block.Block block, org.bukkit.inventory.ItemStack itemStack) { + if (itemStack.getAmount() != 0) { + ItemStack craftItemStack = CraftItemStack.asNMSCopy(itemStack); + ServerLevel serverLevel = ((CraftWorld) block.getWorld()).getHandle(); + Block nmsBlock = serverLevel.getBlockState(new BlockPos(block.getX(), block.getY(), block.getZ())).getBlock(); + + return craftItemStack.getDestroySpeed(nmsBlock.defaultBlockState()); + } + + return 1; + } + + @Override + public double getAttackDamage(org.bukkit.inventory.ItemStack itemStack) { + return getItemStackAttribute(itemStack, Attributes.ATTACK_DAMAGE); + } + + @Override + public double getAttackSpeed(org.bukkit.inventory.ItemStack itemStack) { + return getItemStackAttribute(itemStack, Attributes.ATTACK_SPEED); + } + + private double getItemStackAttribute(org.bukkit.inventory.ItemStack itemStack, Attribute attribute) { + if (itemStack.getAmount() != 0) { + ItemStack craftItemStack = CraftItemStack.asNMSCopy(itemStack); + Multimap attributeMultimap = craftItemStack + .getAttributeModifiers(EquipmentSlot.MAINHAND); + AttributeModifier attributeModifier = Iterables.getFirst(attributeMultimap.get(attribute), null); + + return attributeModifier != null ? attributeModifier.amount : 0; + } + + return 0; + } + + @Override + public Sound getHitSound(org.bukkit.block.Block block) { + try { + ServerLevel serverLevel = ((CraftWorld) block.getWorld()).getHandle(); + Block nmsBlock = serverLevel.getBlockState(new BlockPos(block.getX(), block.getY(), block.getZ())).getBlock(); + SoundType soundType = nmsBlock.getSoundType(nmsBlock.defaultBlockState()); + + return Sound.valueOf(soundType.getHitSound().getLocation().getPath().toUpperCase() + .replace(".", "_")); + } catch (IllegalArgumentException ignored) { + } + + return Sound.BLOCK_STONE_HIT; + } + + @Override + public float getBlockHardness(org.bukkit.block.Block block) { + ServerLevel serverLevel = ((CraftWorld) block.getWorld()).getHandle(); + Block nmsBlock = serverLevel.getBlockState(new BlockPos(block.getX(), block.getY(), block.getZ())).getBlock(); + + return nmsBlock.defaultBlockState().getDestroySpeed(null, null); + } + + @Override + public boolean breakBlock(Player player, org.bukkit.block.Block block) { + return player.breakBlock(block); + } + + @Override + public void setModifier(Player player, double damage, double speed, UUID uuid) { + ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); + AttributeInstance damageAttributeInstance = serverPlayer.getAttribute(Attributes.ATTACK_DAMAGE); + AttributeInstance speedAttributeInstance = serverPlayer.getAttribute(Attributes.ATTACK_SPEED); + + if (damageAttributeInstance != null) { + damageAttributeInstance.addTransientModifier(new AttributeModifier(uuid, "Weapon modifier", damage, + AttributeModifier.Operation.ADDITION)); + } + + if (speedAttributeInstance != null) { + speedAttributeInstance.addTransientModifier(new AttributeModifier(uuid, "Weapon modifier", speed, + AttributeModifier.Operation.ADDITION)); + } + } + + @Override + public void removeModifier(Player player, UUID uuid) { + ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); + AttributeInstance damageAttributeInstance = serverPlayer.getAttribute(Attributes.ATTACK_DAMAGE); + AttributeInstance speedAttributeInstance = serverPlayer.getAttribute(Attributes.ATTACK_SPEED); + + if (damageAttributeInstance != null) { + damageAttributeInstance.removeModifier(uuid); + } + + if (speedAttributeInstance != null) { + speedAttributeInstance.removeModifier(uuid); + } + } + + @Override + public void attack(Player player, org.bukkit.entity.Entity entity) { + (((CraftPlayer) player).getHandle()).attack(((CraftEntity) entity).getHandle()); + } +} \ No newline at end of file