diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RBlockDisplay.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RBlockDisplay.java new file mode 100644 index 00000000..5f4c4d50 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RBlockDisplay.java @@ -0,0 +1,68 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2023 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.entity; + +import de.steamwar.Reflection; +import de.steamwar.core.BountifulWrapper; +import lombok.Getter; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.EntityType; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +/** + * !! This class cannot be used in Versions lower than or equal to 1.19.4 !! + */ +@Getter +public class RBlockDisplay extends RDisplay { + + public static final BlockData DEFAULT_BLOCK = Material.AIR.createBlockData(); + + private BlockData block; + + public RBlockDisplay(REntityServer server, Location location) { + super(server, EntityType.BLOCK_DISPLAY, location); + this.block = DEFAULT_BLOCK; + server.addEntity(this); + } + + @Override + protected void postSpawn(Consumer packetSink) { + super.postSpawn(packetSink); + sendPacket(packetSink, this::getBlock); + } + + public void setBlock(BlockData block) { + this.block = block; + sendPacket(updatePacketSink, this::getBlock); + } + + private static final Class iBlockDataClass = Reflection.getClass("net.minecraft.world.level.block.state.BlockState"); + private static final Reflection.Method getState = Reflection.getTypedMethod(Reflection.getClass("org.bukkit.craftbukkit.block.data.CraftBlockData"), "getState", iBlockDataClass); + private static final Object blockWatcher = BountifulWrapper.impl.getDataWatcherObject(22, iBlockDataClass); + private void getBlock(boolean ignoreDefault, BiConsumer packetSink) { + if (ignoreDefault || !block.getAsString(true).equals(DEFAULT_BLOCK.getAsString(true))) { + packetSink.accept(blockWatcher, getState.invoke(block)); + } + } +} diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RDisplay.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RDisplay.java new file mode 100644 index 00000000..863dc658 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RDisplay.java @@ -0,0 +1,258 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2023 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.entity; + +import de.steamwar.core.BountifulWrapper; +import lombok.Getter; +import lombok.NonNull; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.entity.Display; +import org.bukkit.entity.EntityType; +import org.bukkit.util.Transformation; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +/** + * !! This class cannot be used in Versions lower than or equal to 1.19.4 !! + */ +@Getter +public abstract class RDisplay extends REntity { + + protected final Consumer updatePacketSink = o -> server.updateEntity(this, o); + + public static final Transformation DEFAULT_TRANSFORM = new Transformation(new Vector3f(), new Quaternionf(0, 0, 0, 1), new Vector3f(1, 1, 1), new Quaternionf(0, 0, 0, 1)); + + private Transformation transform; + private int interpolationDuration; + private float viewRange; + private float shadowRadius; + private float shadowStrength; + private float displayWidth; + private float displayHeight; + private int interpolationDelay; + private Display.Billboard billboard; + private Color glowColorOverride; + private Display.Brightness brightness; + + protected RDisplay(REntityServer server, EntityType entityType, Location location) { + super(server, entityType, location, 0); + this.transform = DEFAULT_TRANSFORM; + this.interpolationDuration = 0; + this.viewRange = 1.0F; + this.shadowRadius = 0.0F; + this.shadowStrength = 1.0F; + this.displayWidth = 0.0F; + this.displayHeight = 0.0F; + this.interpolationDelay = 0; + this.billboard = Display.Billboard.FIXED; + this.glowColorOverride = null; + this.brightness = null; + } + + @Override + protected void postSpawn(Consumer packetSink) { + super.postSpawn(packetSink); + sendPacket(packetSink, + this::getTransformData, + this::getInterpolationDuration, + this::getViewRange, + this::getShadowRadius, + this::getShadowStrength, + this::getDisplayWidth, + this::getDisplayHeight, + this::getInterpolationDelay, + this::getBillboard, + this::getGlowColorOverride, + this::getBrightness + ); + } + + @SafeVarargs + protected final void sendPacket(Consumer packetSink, BiConsumer>... dataSinkSinks) { + List keyValueData = new ArrayList<>(); + boolean ignoreDefault = packetSink == updatePacketSink; + for (BiConsumer> dataSinkSink : dataSinkSinks) { + dataSinkSink.accept(ignoreDefault, (dataWatcher, value) -> { + keyValueData.add(dataWatcher); + keyValueData.add(value); + }); + } + if (!keyValueData.isEmpty()) { + packetSink.accept(getDataWatcherPacket(keyValueData.toArray())); + } + } + + public void setTransform(@NonNull Transformation transform) { + this.transform = transform; + sendPacket(updatePacketSink, this::getTransformData); + } + + private static final Object translationWatcher = BountifulWrapper.impl.getDataWatcherObject(10, Vector3f.class); + private static final Object leftRotationWatcher = BountifulWrapper.impl.getDataWatcherObject(12, Quaternionf.class); + private static final Object scaleWatcher = BountifulWrapper.impl.getDataWatcherObject(11, Vector3f.class); + private static final Object rightRotationWatcher = BountifulWrapper.impl.getDataWatcherObject(13, Quaternionf.class); + + private void getTransformData(boolean ignoreDefault, BiConsumer dataSink) { + if (ignoreDefault || !transform.equals(DEFAULT_TRANSFORM)) { + dataSink.accept(translationWatcher, transform.getTranslation()); + dataSink.accept(leftRotationWatcher, transform.getLeftRotation()); + dataSink.accept(scaleWatcher, transform.getScale()); + dataSink.accept(rightRotationWatcher, transform.getRightRotation()); + } + } + + public void setInterpolationDuration(int interpolationDuration) { + this.interpolationDuration = interpolationDuration; + sendPacket(updatePacketSink, this::getInterpolationDuration); + } + + private static final Object transformationInterpolationDurationWatcher = BountifulWrapper.impl.getDataWatcherObject(8, Integer.class); + private static final Object positionOrRotationInterpolationDurationWatcher = BountifulWrapper.impl.getDataWatcherObject(9, Integer.class); + + private void getInterpolationDuration(boolean ignoreDefault, BiConsumer packetSink) { + if (ignoreDefault || interpolationDelay != 0) { + packetSink.accept(transformationInterpolationDurationWatcher, interpolationDuration); + packetSink.accept(positionOrRotationInterpolationDurationWatcher, interpolationDuration); + } + } + + public void setViewRange(float viewRange) { + this.viewRange = viewRange; + sendPacket(updatePacketSink, this::getViewRange); + } + + private static final Object viewRangeWatcher = BountifulWrapper.impl.getDataWatcherObject(16, Float.class); + + private void getViewRange(boolean ignoreDefault, BiConsumer packetSink) { + if (ignoreDefault || viewRange != 1.0F) { + packetSink.accept(viewRangeWatcher, viewRange); + } + } + + public void setShadowRadius(float shadowRadius) { + this.shadowRadius = shadowRadius; + sendPacket(updatePacketSink, this::getShadowRadius); + } + + private static final Object shadowRadiusWatcher = BountifulWrapper.impl.getDataWatcherObject(17, Float.class); + + private void getShadowRadius(boolean ignoreDefault, BiConsumer packetSink) { + if (ignoreDefault || shadowRadius != 0.0F) { + packetSink.accept(shadowRadiusWatcher, shadowRadius); + } + } + + public void setShadowStrength(float shadowStrength) { + this.shadowStrength = shadowStrength; + sendPacket(updatePacketSink, this::getShadowStrength); + } + + private static final Object shadowStrengthWatcher = BountifulWrapper.impl.getDataWatcherObject(18, Float.class); + + private void getShadowStrength(boolean ignoreDefault, BiConsumer packetSink) { + if (ignoreDefault || shadowStrength != 1.0F) { + packetSink.accept(shadowStrengthWatcher, shadowStrength); + } + } + + public void setDisplayWidth(float displayWidth) { + this.displayWidth = displayWidth; + sendPacket(updatePacketSink, this::getDisplayWidth); + } + + private static final Object displayWidthWatcher = BountifulWrapper.impl.getDataWatcherObject(19, Float.class); + + private void getDisplayWidth(boolean ignoreDefault, BiConsumer packetSink) { + if (ignoreDefault || displayWidth != 0.0F) { + packetSink.accept(displayWidthWatcher, displayWidth); + } + } + + public void setDisplayHeight(float displayHeight) { + this.displayHeight = displayHeight; + sendPacket(updatePacketSink, this::getDisplayHeight); + } + + private static final Object displayHeightWatcher = BountifulWrapper.impl.getDataWatcherObject(20, Float.class); + + private void getDisplayHeight(boolean ignoreDefault, BiConsumer packetSink) { + if (ignoreDefault || displayHeight != 0.0F) { + packetSink.accept(displayHeightWatcher, displayHeight); + } + } + + public void setInterpolationDelay(int interpolationDelay) { + this.interpolationDelay = interpolationDelay; + sendPacket(updatePacketSink, this::getInterpolationDelay); + } + + private static final Object interpolationDelayWatcher = BountifulWrapper.impl.getDataWatcherObject(7, Integer.class); + + private void getInterpolationDelay(boolean ignoreDefault, BiConsumer packetSink) { + if (ignoreDefault || interpolationDelay != 0) { + packetSink.accept(interpolationDelayWatcher, interpolationDelay); + } + } + + public void setBillboard(Display.Billboard billboard) { + this.billboard = billboard; + sendPacket(updatePacketSink, this::getBillboard); + } + + private static final Object billboardWatcher = BountifulWrapper.impl.getDataWatcherObject(14, Byte.class); + + private void getBillboard(boolean ignoreDefault, BiConsumer packetSink) { + if (ignoreDefault || billboard != Display.Billboard.FIXED) { + packetSink.accept(billboardWatcher, (byte) billboard.ordinal()); + } + } + + public void setGlowColorOverride(Color glowColorOverride) { + this.glowColorOverride = glowColorOverride; + sendPacket(updatePacketSink, this::getGlowColorOverride); + } + + private static final Object glowColorOverrideWatcher = BountifulWrapper.impl.getDataWatcherObject(21, Integer.class); + + private void getGlowColorOverride(boolean ignoreDefault, BiConsumer packetSink) { + if (ignoreDefault || glowColorOverride != null) { + packetSink.accept(glowColorOverrideWatcher, glowColorOverride == null ? -1 : glowColorOverride.asARGB()); + } + } + + public void setBrightness(Display.Brightness brightness) { + this.brightness = brightness; + sendPacket(updatePacketSink, this::getBrightness); + } + + private static final Object brightnessWatcher = BountifulWrapper.impl.getDataWatcherObject(15, Integer.class); + + private void getBrightness(boolean ignoreDefault, BiConsumer packetSink) { + if (ignoreDefault || brightness != null) { + packetSink.accept(brightnessWatcher, brightness == null ? -1 : brightness.getBlockLight() << 4 | brightness.getSkyLight() << 20); + } + } +} diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/REntity.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/REntity.java index e7ecb1a8..0c02a889 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/REntity.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/REntity.java @@ -45,7 +45,7 @@ public class REntity { private static int entityIdCounter = -1; private static final Random random = new Random(); - private final REntityServer server; + protected final REntityServer server; private final EntityType entityType; @Getter protected final int entityId; @@ -441,7 +441,7 @@ public class REntity { private static final Reflection.Field equipmentEntity = Reflection.getField(ProtocolWrapper.equipmentPacket, int.class, 0); private static final Class craftItemStack = Reflection.getClass("org.bukkit.craftbukkit.inventory.CraftItemStack"); - private static final Reflection.Method asNMSCopy = Reflection.getTypedMethod(REntity.craftItemStack, "asNMSCopy", ProtocolWrapper.itemStack, ItemStack.class); + protected static final Reflection.Method asNMSCopy = Reflection.getTypedMethod(REntity.craftItemStack, "asNMSCopy", ProtocolWrapper.itemStack, ItemStack.class); protected Object getEquipmentPacket(Object slot, ItemStack stack){ Object packet = Reflection.newInstance(ProtocolWrapper.equipmentPacket); equipmentEntity.set(packet, entityId); diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RItemDisplay.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RItemDisplay.java new file mode 100644 index 00000000..52cbfc31 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RItemDisplay.java @@ -0,0 +1,81 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2023 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.entity; + +import de.steamwar.core.BountifulWrapper; +import de.steamwar.core.ProtocolWrapper; +import lombok.Getter; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.ItemDisplay; +import org.bukkit.inventory.ItemStack; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +/** + * !! This class cannot be used in Versions lower than or equal to 1.19.4 !! + */ +@Getter +public class RItemDisplay extends RDisplay { + + public static final ItemStack DEFAULT_ITEM_STACK = new ItemStack(Material.AIR); + + private ItemStack itemStack; + private ItemDisplay.ItemDisplayTransform itemDisplayTransform; + + public RItemDisplay(REntityServer server, Location location) { + super(server, EntityType.ITEM_DISPLAY, location); + this.itemStack = DEFAULT_ITEM_STACK; + this.itemDisplayTransform = ItemDisplay.ItemDisplayTransform.NONE; + server.addEntity(this); + } + + @Override + protected void postSpawn(Consumer packetSink) { + super.postSpawn(packetSink); + sendPacket(packetSink, this::getItemStack, this::getItemDisplayTransform); + } + + public void setItemStack(ItemStack itemStack) { + this.itemStack = itemStack; + sendPacket(updatePacketSink, this::getItemStack); + } + + private static final Object itemStackWatcher = BountifulWrapper.impl.getDataWatcherObject(22, ProtocolWrapper.itemStack); + private void getItemStack(boolean ignoreDefault, BiConsumer packetSink) { + if (ignoreDefault || !itemStack.equals(DEFAULT_ITEM_STACK)) { + packetSink.accept(itemStackWatcher, asNMSCopy.invoke(null, itemStack)); + } + } + + private static final Object itemDisplayTransformWatcher = BountifulWrapper.impl.getDataWatcherObject(23, Byte.class); + public void setItemDisplayTransform(ItemDisplay.ItemDisplayTransform itemDisplayTransform) { + this.itemDisplayTransform = itemDisplayTransform; + sendPacket(updatePacketSink, this::getItemDisplayTransform); + } + + private void getItemDisplayTransform(boolean ignoreDefault, BiConsumer packetSink) { + if (ignoreDefault || itemDisplayTransform != ItemDisplay.ItemDisplayTransform.NONE) { + packetSink.accept(itemDisplayTransformWatcher, (byte) itemDisplayTransform.ordinal()); + } + } +} diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RTextDisplay.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RTextDisplay.java new file mode 100644 index 00000000..605b6770 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RTextDisplay.java @@ -0,0 +1,153 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2023 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.entity; + +import de.steamwar.Reflection; +import de.steamwar.core.BountifulWrapper; +import de.steamwar.core.ChatWrapper; +import lombok.Getter; +import org.bukkit.Location; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.TextDisplay; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +/** + * !! This class cannot be used in Versions lower than or equal to 1.19.4 !! + */ +@Getter +public class RTextDisplay extends RDisplay { + + private String text; + + private int lineWidth; + + private byte textOpacity; + + private boolean shadowed; + + private boolean seeThrough; + + private boolean defaultBackground; + + private TextDisplay.TextAlignment alignment; + + public RTextDisplay(REntityServer server, Location location) { + super(server, EntityType.TEXT_DISPLAY, location); + this.text = ""; + this.lineWidth = 200; + this.textOpacity = (byte) -1; + this.shadowed = false; + this.seeThrough = false; + this.defaultBackground = false; + this.alignment = TextDisplay.TextAlignment.CENTER; + server.addEntity(this); + } + + @Override + protected void postSpawn(Consumer packetSink) { + super.postSpawn(packetSink); + sendPacket(packetSink, this::getText, this::getLineWidth, this::getTextOpacity, this::getTextStatus); + } + + public void setText(String text) { + this.text = text; + sendPacket(updatePacketSink, this::getText); + } + + private static final Class iChatBaseComponent = Reflection.getClass("net.minecraft.network.chat.Component"); + private static final Object textWatcher = BountifulWrapper.impl.getDataWatcherObject(22, iChatBaseComponent); + private void getText(boolean ignoreDefault, BiConsumer packetSink) { + if (ignoreDefault || !text.isEmpty()) { + packetSink.accept(textWatcher, ChatWrapper.impl.stringToChatComponent(text)); + } + } + + public void setLineWidth(int lineWidth) { + this.lineWidth = lineWidth; + sendPacket(updatePacketSink, this::getLineWidth); + } + + private static final Object lineWidthWatcher = BountifulWrapper.impl.getDataWatcherObject(23, Integer.class); + private void getLineWidth(boolean ignoreDefault, BiConsumer packetSink) { + if (ignoreDefault || lineWidth != 200) { + packetSink.accept(lineWidthWatcher, lineWidth); + } + } + + public void setTextOpacity(byte textOpacity) { + this.textOpacity = textOpacity; + sendPacket(updatePacketSink, this::getTextOpacity); + } + + private static final Object textOpacityWatcher = BountifulWrapper.impl.getDataWatcherObject(25, Byte.class); + private void getTextOpacity(boolean ignoreDefault, BiConsumer packetSink) { + if (ignoreDefault || textOpacity != (byte) -1) { + packetSink.accept(textOpacityWatcher, textOpacity); + } + } + + public void setShadowed(boolean shadowed) { + this.shadowed = shadowed; + sendPacket(updatePacketSink, this::getTextStatus); + } + + public void setSeeThrough(boolean seeThrough) { + this.seeThrough = seeThrough; + sendPacket(updatePacketSink, this::getTextStatus); + } + + public void setDefaultBackground(boolean defaultBackground) { + this.defaultBackground = defaultBackground; + sendPacket(updatePacketSink, this::getTextStatus); + } + + public void setAlignment(TextDisplay.TextAlignment alignment) { + this.alignment = alignment; + sendPacket(updatePacketSink, this::getTextStatus); + } + + private static final Object textStatusWatcher = BountifulWrapper.impl.getDataWatcherObject(26, Byte.class); + private void getTextStatus(boolean ignoreDefault, BiConsumer packetSink) { + byte status = 0; + + if (shadowed) { + status |= 0x01; + } + if (seeThrough) { + status |= 0x02; + } + if (defaultBackground) { + status |= 0x04; + } + if (alignment == TextDisplay.TextAlignment.CENTER) { + // Do nothing + } else if (alignment == TextDisplay.TextAlignment.LEFT) { + status |= 0x08; + } else if (alignment == TextDisplay.TextAlignment.RIGHT) { + status |= 0x0F; + } + + if (ignoreDefault || status != 0) { + packetSink.accept(textStatusWatcher, status); + } + } +}