diff --git a/.github/workflows/dev-build-release.yml b/.github/workflows/dev-build-release.yml new file mode 100644 index 0000000..e0ba3da --- /dev/null +++ b/.github/workflows/dev-build-release.yml @@ -0,0 +1,47 @@ +name: Build + +on: + workflow_dispatch: + push: + branches: ['*'] + pull_request: + branches: ['*'] + +permissions: + contents: write + +jobs: + build: + runs-on: ubuntu-latest + env: + TYCOONS_REPO_USER: ${{ secrets.EVOKE_REPO_USERNAME }} + TYCOONS_REPO_PASS: ${{ secrets.EVOKE_REPO_PASSWORD }} + steps: + - name: Clone project + uses: actions/checkout@v4 + + - name: Install JDK 8 + uses: actions/setup-java@v4 + with: + java-version: '8' + distribution: 'temurin' + check-latest: true + + - name: Setup gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Run build & publish with Gradle Wrapper + if: github.ref == 'refs/heads/master' + run: chmod +x ./gradlew && ./gradlew publishAllPublicationsToMavenRepository + + - name: Run build with Gradle Wrapper + if: github.ref != 'refs/heads/master' + run: chmod +x ./gradlew && ./gradlew build + + - name: Create Release + if: github.ref != 'refs/heads/master' + uses: softprops/action-gh-release@v2 + with: + name: '${{ github.ref_name }}: ${{ github.event.head_commit.message }} (${{ github.sha }})' + prerelease: ${{ github.ref != 'refs/heads/master' }} + tag_name: ${{ github.ref_name }}-${{ github.sha }} \ No newline at end of file diff --git a/api/build.gradle.kts b/api/build.gradle.kts index a91defa..bc0b925 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -1,6 +1,7 @@ plugins { entitylib.`shadow-conventions` entitylib.`library-conventions` + `el-version` } dependencies { @@ -8,4 +9,23 @@ dependencies { compileOnly(libs.bundles.adventure) compileOnly(libs.packetevents.api) -} \ No newline at end of file + testCompileOnly(libs.packetevents.api) +} + +tasks { + javadoc { + mustRunAfter(generateVersionsFile) + } + + sourcesJar { + mustRunAfter(generateVersionsFile) + } + + withType { + dependsOn(generateVersionsFile) + } + + generateVersionsFile { + packageName = "me.tofaa.entitylib.utils" + } +} diff --git a/api/src/main/java/me/tofaa/entitylib/EntityLibAPI.java b/api/src/main/java/me/tofaa/entitylib/EntityLibAPI.java index 663c368..77de149 100644 --- a/api/src/main/java/me/tofaa/entitylib/EntityLibAPI.java +++ b/api/src/main/java/me/tofaa/entitylib/EntityLibAPI.java @@ -1,14 +1,9 @@ package me.tofaa.entitylib; import com.github.retrooper.packetevents.PacketEventsAPI; -import com.github.retrooper.packetevents.protocol.entity.type.EntityType; -import com.github.retrooper.packetevents.protocol.player.UserProfile; -import com.github.retrooper.packetevents.protocol.world.Location; import me.tofaa.entitylib.container.EntityContainer; import me.tofaa.entitylib.tick.TickContainer; import me.tofaa.entitylib.wrapper.WrapperEntity; -import me.tofaa.entitylib.wrapper.WrapperPlayer; -import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -58,4 +53,6 @@ public interface EntityLibAPI { @NotNull EntityContainer getDefaultContainer(); + @NotNull + UserLocaleProvider getUserLocaleProvider(); } diff --git a/api/src/main/java/me/tofaa/entitylib/Platform.java b/api/src/main/java/me/tofaa/entitylib/Platform.java index 62accad..fa75430 100644 --- a/api/src/main/java/me/tofaa/entitylib/Platform.java +++ b/api/src/main/java/me/tofaa/entitylib/Platform.java @@ -26,6 +26,13 @@ public interface Platform

{ */ @NotNull EntityUuidProvider getEntityUuidProvider(); + /** + * Gets the provider responsible for retrieving the locale of a user. + * + * @return a non-null {@link UserLocaleProvider} instance. + */ + @NotNull UserLocaleProvider getUserLocaleProvider(); + /** * Sets the entityId integer provider. This can be provided by a platform if needed. * @param provider the entityId integer provider. @@ -38,6 +45,12 @@ public interface Platform

{ */ void setEntityUuidProvider(@NotNull EntityUuidProvider provider); + /** + * Sets the provider responsible for retrieving the locale of a user. + * + * @param provider the {@link UserLocaleProvider} instance to be set. Must not be null. + */ + void setUserLocaleProvider(@NotNull UserLocaleProvider provider); /** * @return the logger EntityLib uses internally. diff --git a/api/src/main/java/me/tofaa/entitylib/UserLocaleProvider.java b/api/src/main/java/me/tofaa/entitylib/UserLocaleProvider.java new file mode 100644 index 0000000..54c542d --- /dev/null +++ b/api/src/main/java/me/tofaa/entitylib/UserLocaleProvider.java @@ -0,0 +1,8 @@ +package me.tofaa.entitylib; + +import java.util.Locale; +import java.util.UUID; + +public interface UserLocaleProvider { + Locale locale(UUID user); +} diff --git a/api/src/main/java/me/tofaa/entitylib/meta/EntityMeta.java b/api/src/main/java/me/tofaa/entitylib/meta/EntityMeta.java index ba03d99..daa4379 100644 --- a/api/src/main/java/me/tofaa/entitylib/meta/EntityMeta.java +++ b/api/src/main/java/me/tofaa/entitylib/meta/EntityMeta.java @@ -228,6 +228,7 @@ public class EntityMeta implements EntityMetadataProvider { /** * Annoying java 8 not letting me do OFFSET + amount in the method call so this is a workaround + * * @param value the value to offset * @param amount the amount to offset by * @return the offset value @@ -271,12 +272,12 @@ public class EntityMeta implements EntityMetadataProvider { } @Override - public List entityData(ClientVersion clientVersion) { + public @NotNull List> entityData(@NotNull ClientVersion clientVersion) { return metadata.getEntries(); // TODO: Atm this is useless cause of the way the api works. Might change in the future } @Override - public List entityData() { + public @NotNull List> entityData() { return metadata.getEntries(); } diff --git a/api/src/main/java/me/tofaa/entitylib/meta/MetaConverterRegistry.java b/api/src/main/java/me/tofaa/entitylib/meta/MetaConverterRegistry.java index bbdbf3f..bdbf82a 100644 --- a/api/src/main/java/me/tofaa/entitylib/meta/MetaConverterRegistry.java +++ b/api/src/main/java/me/tofaa/entitylib/meta/MetaConverterRegistry.java @@ -1,7 +1,6 @@ package me.tofaa.entitylib.meta; import com.github.retrooper.packetevents.protocol.entity.type.EntityType; -import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes; import me.tofaa.entitylib.meta.display.BlockDisplayMeta; import me.tofaa.entitylib.meta.display.ItemDisplayMeta; import me.tofaa.entitylib.meta.display.TextDisplayMeta; @@ -50,9 +49,9 @@ final class MetaConverterRegistry { MetaConverterRegistry() { put(ABSTRACT_WIND_CHARGE, SmallFireballMeta.class, SmallFireballMeta::new); // TODO: Verify correctness put(AREA_EFFECT_CLOUD, AreaEffectCloudMeta.class, AreaEffectCloudMeta::new); + put(ALLAY, LivingEntityMeta.class, LivingEntityMeta::new); // TODO: Implement put(ARMADILLO, ArmadilloMeta.class, ArmadilloMeta::new); // TODO: Verify correctness put(ARMOR_STAND, ArmorStandMeta.class, ArmorStandMeta::new); - put(ALLAY, LivingEntityMeta.class, LivingEntityMeta::new); // TODO: Implement put(ARROW, ArrowMeta.class, ArrowMeta::new); put(AXOLOTL, AxolotlMeta.class, AxolotlMeta::new); put(BAT, BatMeta.class, BatMeta::new); @@ -69,8 +68,9 @@ final class MetaConverterRegistry { put(CHEST_MINECART, ChestMinecartMeta.class, ChestMinecartMeta::new); put(CHICKEN, ChickenMeta.class, ChickenMeta::new); put(COD, CodMeta.class, CodMeta::new); - put(COW, CowMeta.class, CowMeta::new); put(COMMAND_BLOCK_MINECART, CommandBlockMinecartMeta.class, CommandBlockMinecartMeta::new); + put(COW, CowMeta.class, CowMeta::new); + put(CREAKING, LivingEntityMeta.class, LivingEntityMeta::new); // TODO: Implement put(CREEPER, CreeperMeta.class, CreeperMeta::new); put(DOLPHIN, DolphinMeta.class, DolphinMeta::new); put(DONKEY, DonkeyMeta.class, DonkeyMeta::new); @@ -80,11 +80,12 @@ final class MetaConverterRegistry { put(ELDER_GUARDIAN, ElderGuardianMeta.class, ElderGuardianMeta::new); put(END_CRYSTAL, EndCrystalMeta.class, EndCrystalMeta::new); put(ENDER_DRAGON, EnderDragonMeta.class, EnderDragonMeta::new); + put(ENDER_PEARL, ThrownEnderPearlMeta.class, ThrownEnderPearlMeta::new); put(ENDERMAN, EndermanMeta.class, EndermanMeta::new); put(ENDERMITE, EndermiteMeta.class, EndermiteMeta::new); put(EVOKER, EvokerMeta.class, EvokerMeta::new); - put(EYE_OF_ENDER, EyeOfEnderMeta.class, EyeOfEnderMeta::new); put(EVOKER_FANGS, EvokerFangsMeta.class, EvokerFangsMeta::new); + put(EYE_OF_ENDER, EyeOfEnderMeta.class, EyeOfEnderMeta::new); put(FALLING_BLOCK, FallingBlockMeta.class, FallingBlockMeta::new); put(FIREBALL, LargeFireballMeta.class, LargeFireballMeta::new); // TODO: Verify correctness put(FIREWORK_ROCKET, FireworkRocketMeta.class, FireworkRocketMeta::new); @@ -92,11 +93,13 @@ final class MetaConverterRegistry { put(FOX, FoxMeta.class, FoxMeta::new); put(FROG, FrogMeta.class, FrogMeta::new); put(FURNACE_MINECART, FurnaceMinecartMeta.class, FurnaceMinecartMeta::new); + put(GHAST, GhastMeta.class, GhastMeta::new); put(GIANT, GiantMeta.class, GiantMeta::new); put(GLOW_ITEM_FRAME, GlowItemFrameMeta.class, GlowItemFrameMeta::new); put(GLOW_SQUID, GlowSquidMeta.class, GlowSquidMeta::new); put(GOAT, GoatMeta.class, GoatMeta::new); put(GUARDIAN, GuardianMeta.class, GuardianMeta::new); + put(HAPPY_GHAST, GhastMeta.class, GhastMeta::new); // TODO: Implement put(HOGLIN, HoglinMeta.class, HoglinMeta::new); put(HOPPER_MINECART, FurnaceMinecartMeta.class, FurnaceMinecartMeta::new); put(HORSE, HorseMeta.class, HorseMeta::new); @@ -104,35 +107,35 @@ final class MetaConverterRegistry { put(ILLUSIONER, IllusionerMeta.class, IllusionerMeta::new); put(INTERACTION, InteractionMeta.class, InteractionMeta::new); put(IRON_GOLEM, IronGolemMeta.class, IronGolemMeta::new); + put(ITEM, ItemEntityMeta.class, ItemEntityMeta::new); put(ITEM_DISPLAY, ItemDisplayMeta.class, ItemDisplayMeta::new); put(ITEM_FRAME, ItemFrameMeta.class, ItemFrameMeta::new); - put(ITEM, ItemEntityMeta.class, ItemEntityMeta::new); put(LEASH_KNOT, LeashKnotMeta.class, LeashKnotMeta::new); put(LIGHTNING_BOLT, LightningBoltMeta.class, LightningBoltMeta::new); put(LLAMA, LlamaMeta.class, LlamaMeta::new); put(LLAMA_SPIT, LlamaSpitMeta.class, LlamaSpitMeta::new); put(MAGMA_CUBE, MagmaCubeMeta.class, MagmaCubeMeta::new); put(MARKER, MarkerMeta.class, MarkerMeta::new); + put(MOOSHROOM, MooshroomMeta.class, MooshroomMeta::new); put(MULE, MuleMeta.class, MuleMeta::new); put(OCELOT, OcelotMeta.class, OcelotMeta::new); put(PAINTING, PaintingMeta.class, PaintingMeta::new); put(PANDA, PandaMeta.class, PandaMeta::new); - put(POTION, ThrownPotionMeta.class, ThrownPotionMeta::new); put(PARROT, ParrotMeta.class, ParrotMeta::new); + put(PHANTOM, PhantomMeta.class, PhantomMeta::new); put(PIG, PigMeta.class, PigMeta::new); put(PIGLIN, PiglinMeta.class, PiglinMeta::new); put(PIGLIN_BRUTE, PiglinBruteMeta.class, PiglinBruteMeta::new); put(PILLAGER, PillagerMeta.class, PillagerMeta::new); put(PLAYER, PlayerMeta.class, PlayerMeta::new); put(POLAR_BEAR, PolarBearMeta.class, PolarBearMeta::new); - put(POTION, ThrownTridentMeta.class, ThrownTridentMeta::new); + put(POTION, ThrownPotionMeta.class, ThrownPotionMeta::new); put(PRIMED_TNT, PrimedTntMeta.class, PrimedTntMeta::new); put(PUFFERFISH, PufferFishMeta.class, PufferFishMeta::new); put(RABBIT, RabbitMeta.class, RabbitMeta::new); put(RAVAGER, RavagerMeta.class, RavagerMeta::new); put(SALMON, SalmonMeta.class, SalmonMeta::new); put(SHEEP, SheepMeta.class, SheepMeta::new); - put(SNOWBALL, SnowballMeta.class, SnowballMeta::new); put(SHULKER, ShulkerMeta.class, ShulkerMeta::new); put(SHULKER_BULLET, ShulkerBulletMeta.class, ShulkerBulletMeta::new); put(SILVERFISH, SilverfishMeta.class, SilverfishMeta::new); @@ -142,14 +145,16 @@ final class MetaConverterRegistry { put(SMALL_FIREBALL, SmallFireballMeta.class, SmallFireballMeta::new); put(SNIFFER, SnifferMeta.class, SnifferMeta::new); put(SNOW_GOLEM, SnowGolemMeta.class, SnowGolemMeta::new); + put(SNOWBALL, SnowballMeta.class, SnowballMeta::new); put(SPAWNER_MINECART, SpawnerMinecartMeta.class, SpawnerMinecartMeta::new); put(SPIDER, SpiderMeta.class, SpiderMeta::new); + put(SQUID, SquidMeta.class, SquidMeta::new); put(STRAY, StrayMeta.class, StrayMeta::new); put(STRIDER, StriderMeta.class, StriderMeta::new); put(TADPOLE, LivingEntityMeta.class, LivingEntityMeta::new); // TODO: Implement put(TEXT_DISPLAY, TextDisplayMeta.class, TextDisplayMeta::new); put(THROWN_EXP_BOTTLE, ThrownExpBottleMeta.class, ThrownExpBottleMeta::new); - put(ENDER_PEARL, ThrownEnderPearlMeta.class, ThrownEnderPearlMeta::new); + put(TNT, TntMeta.class, TntMeta::new); put(TNT_MINECART, TntMinecartMeta.class, TntMinecartMeta::new); put(TRADER_LLAMA, TraderLlamaMeta.class, TraderLlamaMeta::new); put(TRIDENT, ThrownTridentMeta.class, ThrownTridentMeta::new); @@ -186,4 +191,4 @@ final class MetaConverterRegistry { return converters.getOrDefault(entityType, EntityMeta::new); } -} +} \ No newline at end of file diff --git a/api/src/main/java/me/tofaa/entitylib/meta/Metadata.java b/api/src/main/java/me/tofaa/entitylib/meta/Metadata.java index 5f91115..d93666f 100644 --- a/api/src/main/java/me/tofaa/entitylib/meta/Metadata.java +++ b/api/src/main/java/me/tofaa/entitylib/meta/Metadata.java @@ -19,8 +19,8 @@ public class Metadata { private final int entityId; private volatile boolean notifyAboutChanges = true; - private final HashMap notNotifiedChanges = new HashMap<>(); - private final ConcurrentHashMap metadataMap = new ConcurrentHashMap<>(); + private final HashMap> notNotifiedChanges = new HashMap<>(); + private final ConcurrentHashMap> metadataMap = new ConcurrentHashMap<>(); public Metadata(int entityId) { this.entityId = entityId; @@ -47,13 +47,13 @@ public class Metadata { } public T getIndex(byte index, @Nullable T defaultValue) { - EntityData value = this.metadataMap.get(index); + EntityData value = this.metadataMap.get(index); return value != null ? (T) value.getValue() : defaultValue; } public void setIndex(byte index, @NotNull EntityDataType dataType, T value) { - final EntityData entry = new EntityData(index, dataType, value); + final EntityData entry = new EntityData<>(index, dataType, value); this.metadataMap.put(index, entry); final Optional> optionalApi = EntityLib.getOptionalApi(); @@ -75,7 +75,7 @@ public class Metadata { return; } - List entries = null; + List> entries = null; synchronized (this.notNotifiedChanges) { this.notifyAboutChanges = notifyAboutChanges; if (notifyAboutChanges) { @@ -96,7 +96,7 @@ public class Metadata { } public void setMetaFromPacket(WrapperPlayServerEntityMetadata wrapper) { - for (EntityData data : wrapper.getEntityMetadata()) { + for (EntityData data : wrapper.getEntityMetadata()) { metadataMap.put((byte) data.getIndex(), data); } } @@ -105,7 +105,7 @@ public class Metadata { return notifyAboutChanges; } - @NotNull List getEntries() { + @NotNull List> getEntries() { return Collections.unmodifiableList(new ArrayList<>(metadataMap.values())); } diff --git a/api/src/main/java/me/tofaa/entitylib/meta/display/BlockDisplayMeta.java b/api/src/main/java/me/tofaa/entitylib/meta/display/BlockDisplayMeta.java index 5d1ff50..70d45c3 100644 --- a/api/src/main/java/me/tofaa/entitylib/meta/display/BlockDisplayMeta.java +++ b/api/src/main/java/me/tofaa/entitylib/meta/display/BlockDisplayMeta.java @@ -1,6 +1,8 @@ package me.tofaa.entitylib.meta.display; +import com.github.retrooper.packetevents.PacketEvents; import com.github.retrooper.packetevents.protocol.entity.data.EntityDataTypes; +import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState; import me.tofaa.entitylib.meta.Metadata; public class BlockDisplayMeta extends AbstractDisplayMeta { @@ -20,4 +22,11 @@ public class BlockDisplayMeta extends AbstractDisplayMeta { super.metadata.setIndex(OFFSET, EntityDataTypes.BLOCK_STATE, blockId); } + public WrappedBlockState getBlockState() { + return WrappedBlockState.getByGlobalId(PacketEvents.getAPI().getServerManager().getVersion().toClientVersion(), getBlockId()); + } + + public void setBlockState(WrappedBlockState blockState) { + setBlockId(blockState.getGlobalId()); + } } diff --git a/api/src/main/java/me/tofaa/entitylib/meta/mobs/golem/ShulkerMeta.java b/api/src/main/java/me/tofaa/entitylib/meta/mobs/golem/ShulkerMeta.java index 88617a9..67f5132 100644 --- a/api/src/main/java/me/tofaa/entitylib/meta/mobs/golem/ShulkerMeta.java +++ b/api/src/main/java/me/tofaa/entitylib/meta/mobs/golem/ShulkerMeta.java @@ -1,10 +1,12 @@ package me.tofaa.entitylib.meta.mobs.golem; +import com.github.retrooper.packetevents.protocol.color.DyeColor; import com.github.retrooper.packetevents.protocol.entity.data.EntityDataTypes; import com.github.retrooper.packetevents.protocol.world.Direction; import com.github.retrooper.packetevents.util.Vector3i; import me.tofaa.entitylib.meta.Metadata; import me.tofaa.entitylib.meta.types.MobMeta; +import net.kyori.adventure.text.format.NamedTextColor; import java.util.Optional; @@ -13,42 +15,43 @@ public class ShulkerMeta extends MobMeta { public static final byte OFFSET = MobMeta.MAX_OFFSET; public static final byte MAX_OFFSET = OFFSET + 1; + private static final DyeColor[] DYE_COLORS = DyeColor.values(); + public ShulkerMeta(int entityId, Metadata metadata) { super(entityId, metadata); } public Direction getAttachFace() { - return super.metadata.getIndex(OFFSET, Direction.DOWN); + return super.metadata.getIndex((byte)16, Direction.DOWN); } public void setAttachFace(Direction value) { - super.metadata.setIndex(OFFSET, EntityDataTypes.INT, value.ordinal()); - } - - public Optional getAttachmentPosition() { - return super.metadata.getIndex(offset(OFFSET, 1), Optional.empty()); - } - - public void setAttachmentPosition(Vector3i value) { - super.metadata.setIndex(offset(OFFSET, 1), EntityDataTypes.OPTIONAL_BLOCK_POSITION, Optional.of(value)); + super.metadata.setIndex((byte)16, EntityDataTypes.INT, value.ordinal()); } public byte getShieldHeight() { - return super.metadata.getIndex(offset(OFFSET, 2), (byte) 0); + return super.metadata.getIndex(offset(OFFSET, 1), (byte) 0); } public void setShieldHeight(byte value) { - super.metadata.setIndex(offset(OFFSET, 2), EntityDataTypes.BYTE, value); + super.metadata.setIndex(offset(OFFSET, 1), EntityDataTypes.BYTE, value); } public byte getColor() { - return super.metadata.getIndex(offset(OFFSET, 3), (byte) 10); + return super.metadata.getIndex(offset(OFFSET, 2), (byte) 16); + } + + public DyeColor getColorEnum() { + return DYE_COLORS[getColor()]; } public void setColor(byte value) { - super.metadata.setIndex(offset(OFFSET, 3), EntityDataTypes.BYTE, value); + super.metadata.setIndex(offset(OFFSET, 2), EntityDataTypes.BYTE, value); } + public void setColor(DyeColor color) { + setColor((byte)color.ordinal()); + } } diff --git a/api/src/main/java/me/tofaa/entitylib/meta/mobs/horse/BaseHorseMeta.java b/api/src/main/java/me/tofaa/entitylib/meta/mobs/horse/BaseHorseMeta.java index 1dd8637..f39cd7a 100644 --- a/api/src/main/java/me/tofaa/entitylib/meta/mobs/horse/BaseHorseMeta.java +++ b/api/src/main/java/me/tofaa/entitylib/meta/mobs/horse/BaseHorseMeta.java @@ -1,16 +1,12 @@ package me.tofaa.entitylib.meta.mobs.horse; -import com.github.retrooper.packetevents.protocol.entity.data.EntityDataTypes; import me.tofaa.entitylib.meta.Metadata; -import me.tofaa.entitylib.meta.types.MobMeta; +import me.tofaa.entitylib.meta.types.AgeableMeta; -import java.util.Optional; -import java.util.UUID; +public abstract class BaseHorseMeta extends AgeableMeta { -public abstract class BaseHorseMeta extends MobMeta { - - public static final byte OFFSET = MobMeta.MAX_OFFSET; - public static final byte MAX_OFFSET = OFFSET + 2; + public static final byte OFFSET = AgeableMeta.MAX_OFFSET; + public static final byte MAX_OFFSET = OFFSET + 1; private final static byte TAMED_BIT = 0x02; private final static byte SADDLED_BIT = 0x04; @@ -70,13 +66,4 @@ public abstract class BaseHorseMeta extends MobMeta { public void setMouthOpen(boolean value) { setMaskBit(OFFSET, MOUTH_OPEN_BIT, value); } - - public Optional getOwner() { - return super.metadata.getIndex(offset(OFFSET, 1), Optional.empty()); - } - - public void setOwner(UUID value) { - super.metadata.setIndex(offset(OFFSET, 1), EntityDataTypes.OPTIONAL_UUID, Optional.of(value)); - } - } diff --git a/api/src/main/java/me/tofaa/entitylib/meta/mobs/monster/EndermanMeta.java b/api/src/main/java/me/tofaa/entitylib/meta/mobs/monster/EndermanMeta.java index 0ba5c73..bebef25 100644 --- a/api/src/main/java/me/tofaa/entitylib/meta/mobs/monster/EndermanMeta.java +++ b/api/src/main/java/me/tofaa/entitylib/meta/mobs/monster/EndermanMeta.java @@ -1,11 +1,12 @@ package me.tofaa.entitylib.meta.mobs.monster; +import com.github.retrooper.packetevents.PacketEvents; import com.github.retrooper.packetevents.protocol.entity.data.EntityDataTypes; +import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState; import me.tofaa.entitylib.meta.Metadata; import me.tofaa.entitylib.meta.types.MobMeta; -import org.jetbrains.annotations.Nullable; - import java.util.Optional; +import org.jetbrains.annotations.Nullable; public class EndermanMeta extends MobMeta { @@ -24,6 +25,20 @@ public class EndermanMeta extends MobMeta { super.metadata.setIndex(OFFSET, EntityDataTypes.OPTIONAL_INT, Optional.ofNullable(value)); } + public WrappedBlockState getCarriedBlockState() { + Integer carriedBlockID = getCarriedBlockID(); + if (carriedBlockID == null) return null; + return WrappedBlockState.getByGlobalId(PacketEvents.getAPI().getServerManager().getVersion().toClientVersion(), carriedBlockID); + } + + public void setCarriedBlockState(WrappedBlockState blockState) { + if (blockState == null) { + setCarriedBlockID(null); + return; + } + setCarriedBlockID(blockState.getGlobalId()); + } + public boolean isScreaming() { return super.metadata.getIndex(offset(OFFSET, 1), false); } @@ -33,7 +48,7 @@ public class EndermanMeta extends MobMeta { } public boolean isStaring() { - return super.metadata.getIndex(offset(OFFSET, 2), false); + return super.metadata.getIndex(offset(OFFSET, 2), false); } public void setStaring(boolean value) { diff --git a/api/src/main/java/me/tofaa/entitylib/meta/other/FallingBlockMeta.java b/api/src/main/java/me/tofaa/entitylib/meta/other/FallingBlockMeta.java index ff556ff..c7e979a 100644 --- a/api/src/main/java/me/tofaa/entitylib/meta/other/FallingBlockMeta.java +++ b/api/src/main/java/me/tofaa/entitylib/meta/other/FallingBlockMeta.java @@ -1,6 +1,8 @@ package me.tofaa.entitylib.meta.other; +import com.github.retrooper.packetevents.PacketEvents; import com.github.retrooper.packetevents.protocol.entity.data.EntityDataTypes; +import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState; import com.github.retrooper.packetevents.util.Vector3i; import me.tofaa.entitylib.meta.EntityMeta; import me.tofaa.entitylib.meta.Metadata; @@ -34,6 +36,14 @@ public class FallingBlockMeta extends EntityMeta implements ObjectData { this.blockStateId = blockStateId; } + public WrappedBlockState getBlockState() { + return WrappedBlockState.getByGlobalId(PacketEvents.getAPI().getServerManager().getVersion().toClientVersion(), getBlockStateId()); + } + + public void setBlockState(WrappedBlockState blockState) { + setBlockStateId(blockState.getGlobalId()); + } + @Override public int getObjectData() { return blockStateId; diff --git a/api/src/main/java/me/tofaa/entitylib/meta/other/TntMeta.java b/api/src/main/java/me/tofaa/entitylib/meta/other/TntMeta.java new file mode 100644 index 0000000..ee155aa --- /dev/null +++ b/api/src/main/java/me/tofaa/entitylib/meta/other/TntMeta.java @@ -0,0 +1,42 @@ +package me.tofaa.entitylib.meta.other; + +import com.github.retrooper.packetevents.PacketEvents; +import com.github.retrooper.packetevents.protocol.entity.data.EntityDataTypes; +import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState; +import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes; +import me.tofaa.entitylib.meta.EntityMeta; +import me.tofaa.entitylib.meta.Metadata; + +public class TntMeta extends EntityMeta { + public static final byte OFFSET = EntityMeta.MAX_OFFSET; + public static final byte MAX_OFFSET = OFFSET + 2; + + public TntMeta(int entityId, Metadata metadata) { + super(entityId, metadata); + } + + public int getFuseTime() { + return super.metadata.getIndex(OFFSET, 80); + } + + public void setFuseTime(int value) { + super.metadata.setIndex(OFFSET, EntityDataTypes.INT, value); + } + + public int getBlockData() { + return super.metadata.getIndex(offset(OFFSET, 1), StateTypes.TNT.createBlockState().getGlobalId()); + } + + public void setBlockData(int blockData) { + super.metadata.setIndex(offset(OFFSET, 1), EntityDataTypes.BLOCK_STATE, blockData); + } + + public WrappedBlockState getBlockState() { + return WrappedBlockState.getByGlobalId(PacketEvents.getAPI().getServerManager().getVersion().toClientVersion(), getBlockData()); + } + + public void setBlockState(WrappedBlockState blockState) { + setBlockData(blockState.getGlobalId()); + } + +} diff --git a/api/src/main/java/me/tofaa/entitylib/utils/PacketUtil.java b/api/src/main/java/me/tofaa/entitylib/utils/PacketUtil.java new file mode 100644 index 0000000..9e3b130 --- /dev/null +++ b/api/src/main/java/me/tofaa/entitylib/utils/PacketUtil.java @@ -0,0 +1,34 @@ +package me.tofaa.entitylib.utils; + +import com.github.retrooper.packetevents.protocol.entity.data.EntityData; +import com.github.retrooper.packetevents.protocol.entity.data.EntityDataTypes; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityMetadata; +import me.tofaa.entitylib.EntityLib; +import java.util.Locale; +import java.util.Optional; +import java.util.UUID; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.translation.GlobalTranslator; + +public class PacketUtil { + private PacketUtil() { + } + + public static void renderPacket(UUID user, WrapperPlayServerEntityMetadata metadata) { + Locale locale = EntityLib.getApi().getUserLocaleProvider().locale(user); + for (final EntityData entityData : metadata.getEntityMetadata()) { + if (entityData.getType() == EntityDataTypes.ADV_COMPONENT) { + Component component = (Component) entityData.getValue(); + final Component rendered = GlobalTranslator.render(component, locale); + ((EntityData) entityData).setValue(rendered); + } else if (entityData.getType() == EntityDataTypes.OPTIONAL_ADV_COMPONENT) { + final Optional optional = (Optional) entityData.getValue(); + if (optional.isPresent()) { + final Component component = optional.get(); + final Component rendered = GlobalTranslator.render(component, locale); + ((EntityData>) entityData).setValue(Optional.of(rendered)); + } + } + } + } +} diff --git a/api/src/main/java/me/tofaa/entitylib/ve/ViewerEngine.java b/api/src/main/java/me/tofaa/entitylib/ve/ViewerEngine.java index b1036c7..0d3630f 100644 --- a/api/src/main/java/me/tofaa/entitylib/ve/ViewerEngine.java +++ b/api/src/main/java/me/tofaa/entitylib/ve/ViewerEngine.java @@ -25,21 +25,57 @@ public class ViewerEngine { private final Set tracked; private final ViewerEngineListener listener; private Executor executor; + private boolean enabled = false; + + /** + * Creates an instance of ViewerEngine + * It is recommended to specify explicitly the Executor that should be used. + */ public ViewerEngine() { + this(Executors.newSingleThreadExecutor()); + } + + /** + * Creates an instance of ViewerEngine with a specific executor. Depending on your rules one thread might not be enough + * @param executor The executor that is used to process entity tracking. + */ + public ViewerEngine(Executor executor) { this.globalRules = new CopyOnWriteArrayList<>(); this.tracked = Collections.newSetFromMap(new WeakHashMap<>()); - this.executor = Executors.newSingleThreadExecutor(); + this.executor = executor; this.listener = new ViewerEngineListener(this); } + /** + * Enables this viewer engine. + * Registers a viewer engine listener to handle tracking + */ public void enable() { + if (enabled) { + return; + } + enabled = true; EntityLib.getApi().getPacketEvents().getEventManager().registerListener(listener); } + + + /** + * Disables this viewer engine. + * Unregisters the viewer engine listener that handles tracking. + */ public void disable() { + if (!enabled) { + return; + } + enabled = false; EntityLib.getApi().getPacketEvents().getEventManager().unregisterListener(listener); } + /** + * Refreshes and updates every tracked by this viewer engine entities viewers to see if they follow the spawning rules. + * If they do not they will no longer see the entity; + */ public void refresh() { getTracked0().forEach(entity -> { for (UUID viewer : entity.getViewers()) { @@ -58,19 +94,40 @@ public class ViewerEngine { this.executor = executor; } + /** + * Tells this ViewerEngine to begin tracking a specific {@link WrapperEntity} + * @param entity the entity to begin tracking. + */ public void track(@NotNull WrapperEntity entity) { tracked.add(entity); } + /** + * Tells this ViewerEngine to stop tracking a specific {@link WrapperEntity} + * @param entity the entity to stop tracking. + */ + public void untrack(@NotNull WrapperEntity entity) { + tracked.remove(entity); + } + public void clearTracked() { tracked.clear(); } + /** + * Checks if a viewer/user validates every viewer rule handled by this viewer engine or not. + * @param user The user to check + * @param entity The entity that is getting its own viewer rules checked as well as the global defined one with {@link ViewerEngine#addViewerRule(ViewerRule)} + * @return true if the user passed and did not fail any rules, false otherwise + */ public boolean canSpawnFor(User user, WrapperEntity entity) { if (entity.getViewerRules().stream().anyMatch(rule -> rule.shouldSee(user))) return true; return globalRules.stream().anyMatch(rule -> rule.shouldSee(user)); } + /** + * Same as {@link ViewerEngine#canSpawnFor(User, WrapperEntity)} but with UUID instead of User + */ public boolean canSpawnFor(UUID userId, WrapperEntity entity) { User user = EntityLib.getApi().getPacketEvents().getProtocolManager().getUser( EntityLib.getApi().getPacketEvents().getProtocolManager().getChannel(userId) @@ -79,6 +136,9 @@ public class ViewerEngine { return canSpawnFor(user, entity); } + /** + * @return An unmodifiable view of the entities that are being tracked. + */ public @UnmodifiableView Collection getTracked() { return Collections.unmodifiableCollection(tracked); } diff --git a/api/src/main/java/me/tofaa/entitylib/wrapper/WrapperEntity.java b/api/src/main/java/me/tofaa/entitylib/wrapper/WrapperEntity.java index a0c211f..1e37128 100644 --- a/api/src/main/java/me/tofaa/entitylib/wrapper/WrapperEntity.java +++ b/api/src/main/java/me/tofaa/entitylib/wrapper/WrapperEntity.java @@ -5,31 +5,44 @@ import com.github.retrooper.packetevents.protocol.player.User; import com.github.retrooper.packetevents.protocol.world.Location; import com.github.retrooper.packetevents.util.Vector3d; import com.github.retrooper.packetevents.wrapper.PacketWrapper; -import com.github.retrooper.packetevents.wrapper.play.server.*; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerBundle; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerDestroyEntities; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityHeadLook; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityMetadata; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityRotation; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityTeleport; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityVelocity; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerSetPassengers; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerSpawnEntity; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerSystemChatMessage; import me.tofaa.entitylib.EntityLib; import me.tofaa.entitylib.container.EntityContainer; import me.tofaa.entitylib.meta.EntityMeta; import me.tofaa.entitylib.meta.types.ObjectData; import me.tofaa.entitylib.tick.Tickable; +import me.tofaa.entitylib.utils.PacketUtil; import me.tofaa.entitylib.ve.ViewerRule; import me.tofaa.entitylib.wrapper.spawning.SpawnPacketProvider; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; import net.kyori.adventure.text.Component; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnmodifiableView; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.function.Consumer; - public class WrapperEntity implements Tickable { - private final UUID uuid; private final int entityId; - private EntityType entityType; - private EntityMeta entityMeta; + private final EntityType entityType; + private final EntityMeta entityMeta; private boolean ticking; protected Location location; private Location preRidingLocation; @@ -62,6 +75,7 @@ public class WrapperEntity implements Tickable { public WrapperEntity(UUID uuid, EntityType entityType) { this(EntityLib.getPlatform().getEntityIdProvider().provide(uuid, entityType), uuid, entityType); } + public WrapperEntity(EntityType entityType) { this(EntityLib.getPlatform().getEntityUuidProvider().provide(entityType), entityType); } @@ -72,25 +86,20 @@ public class WrapperEntity implements Tickable { public boolean spawn(Location location, EntityContainer parent) { if (spawned) return false; + this.location = location; this.spawned = true; + sendPacketsToViewers( - new WrapperPlayServerSpawnEntity( - entityId, - Optional.of(this.uuid), - entityType, - location.getPosition(), - location.getPitch(), - location.getYaw(), - location.getYaw(), - getObjectData(), - createVeloPacket() - ), + createSpawnPacket(), entityMeta.createPacket() ); + if (this instanceof WrapperLivingEntity) { - sendPacketsToViewers(((WrapperLivingEntity)this).getAttributes().createPacket()); + WrapperLivingEntity wrapperLivingEntity = (WrapperLivingEntity) this; + wrapperLivingEntity.createSpawnPackets().forEach(this::sendPacketsToViewers); } + this.parent = parent; parent.addEntity(this); return true; @@ -101,7 +110,7 @@ public class WrapperEntity implements Tickable { return SpawnPacketProvider.GENERAL.provide(this); } - public boolean spawn(Location location) { + public boolean spawn(@NotNull Location location) { return spawn(location, EntityLib.getApi().getDefaultContainer()); } @@ -110,6 +119,7 @@ public class WrapperEntity implements Tickable { if (entityMeta instanceof ObjectData) { return ((ObjectData) entityMeta).getObjectData(); } + return 0; } @@ -117,6 +127,7 @@ public class WrapperEntity implements Tickable { public Optional createVeloPacket() { Optional velocity; double veloX = 0, veloY = 0, veloZ = 0; + if (entityMeta instanceof ObjectData) { ObjectData od = (ObjectData) entityMeta; if (od.requiresVelocityPacketAtSpawn()) { @@ -126,11 +137,13 @@ public class WrapperEntity implements Tickable { veloZ = veloPacket.getVelocity().getZ(); } } + if (veloX == 0 && veloY == 0 && veloZ == 0) { velocity = Optional.of(Vector3d.zero()); } else { velocity = Optional.of(new Vector3d(veloX, veloY, veloZ)); } + return velocity; } @@ -141,37 +154,37 @@ public class WrapperEntity implements Tickable { public void remove() { if (parent != null) { parent.removeEntity(this, true); - } - else { + } else { despawn(); } } public void despawn() { if (!spawned) return; + spawned = false; + if (this instanceof WrapperPlayer) { WrapperPlayer p = (WrapperPlayer) this; sendPacketsToViewers(p.tabListRemovePacket()); } + sendPacketToViewers(new WrapperPlayServerDestroyEntities(entityId)); } public void teleport(@NotNull Location location, boolean onGround) { - if (!spawned) { - return; - } + if (!spawned) return; + this.location = location; this.onGround = onGround; - sendPacketToViewers( - new WrapperPlayServerEntityTeleport( - entityId, - location.getPosition(), - location.getYaw(), - location.getPitch(), - onGround - ) - ); + + sendPacketToViewers(new WrapperPlayServerEntityTeleport( + entityId, + location.getPosition(), + location.getYaw(), + location.getPitch(), + onGround + )); } public void teleport(@NotNull Location location) { @@ -180,30 +193,38 @@ public class WrapperEntity implements Tickable { /** * Adds a viewer to the viewers set. The viewer will receive all packets and be informed of this addition + * * @param uuid the uuid of the user to add */ - public void addViewer(UUID uuid) { + public void addViewer(@NotNull UUID uuid) { if (!viewers.add(uuid)) { return; } + if (location == null) { if (EntityLib.getApi().getSettings().isDebugMode()) { EntityLib.getPlatform().getLogger().warning("Location is null for entity " + entityId + ". Cannot spawn."); } + return; } + if (spawned) { if (this instanceof WrapperPlayer) { WrapperPlayer p = (WrapperPlayer) this; sendPacket(uuid, p.tabListPacket()); } + sendPacket(uuid, createSpawnPacket()); sendPacket(uuid, entityMeta.createPacket()); sendPacket(uuid, createPassengerPacket()); + if (this instanceof WrapperLivingEntity) { - sendPacket(uuid, ((WrapperLivingEntity)this).getAttributes().createPacket()); + WrapperLivingEntity wrapperLivingEntity = (WrapperLivingEntity) this; + wrapperLivingEntity.createSpawnPackets().forEach(packetWrapper -> sendPacket(uuid, packetWrapper)); } } + if (EntityLib.getApi().getSettings().isDebugMode()) { EntityLib.getPlatform().getLogger().info("Added viewer " + uuid + " to entity " + entityId); } @@ -221,7 +242,7 @@ public class WrapperEntity implements Tickable { sendPacketToViewers(new WrapperPlayServerSystemChatMessage(true, message)); } - protected WrapperPlayServerSpawnEntity createSpawnPacket() { + protected PacketWrapper createSpawnPacket() { return new WrapperPlayServerSpawnEntity( entityId, Optional.of(this.uuid), @@ -235,62 +256,70 @@ public class WrapperEntity implements Tickable { ); } - public void addViewer(User user) { + public void addViewer(@NotNull User user) { addViewer(user.getUUID()); } /** * Adds a viewer silently into the viewers set. The viewer will not receive any packets or be informed of this addition + * * @param uuid the uuid of the user to add */ - public void addViewerSilently(UUID uuid) { + public void addViewerSilently(@NotNull UUID uuid) { viewers.add(uuid); } /** * Adds a viewer silently into the viewers set. The viewer will not receive any packets or be informed of this addition + * * @param user the user to add */ - public void addViewerSilently(User user) { + public void addViewerSilently(@NotNull User user) { addViewerSilently(user.getUUID()); } /** * Removes a viewer from the viewers set of this entity. The viewer will be informed of this removal and will no longer receive any packets + * * @param uuid the uuid of the user to remove */ - public void removeViewer(UUID uuid) { + public void removeViewer(@NotNull UUID uuid) { if (!viewers.remove(uuid)) { return; } + if (this instanceof WrapperPlayer) { WrapperPlayer p = (WrapperPlayer) this; sendPacket(uuid, p.tabListRemovePacket()); } + sendPacket(uuid, new WrapperPlayServerDestroyEntities(entityId)); } /** * Removes a viewer from the viewers set of this entity. The viewer will be informed of this removal and will no longer receive any packets + * * @param user the user to remove */ - public void removeViewer(User user) { + public void removeViewer(@NotNull User user) { removeViewer(user.getUUID()); } /** * removes a viewer silently into the viewers set. The viewer will not receive any packets or be informed of this removal + * * @param uuid of the user to remove */ - public void removeViewerSilently(UUID uuid) { + public void removeViewerSilently(@NotNull UUID uuid) { viewers.remove(uuid); } /** * removes a viewer silently into the viewers set. The viewer will not receive any packets or be informed of this removal + * * @param user the user to remove */ - public void removeViewerSilently(User user) { + public void removeViewerSilently(@NotNull User user) { removeViewerSilently(user.getUUID()); } @@ -298,11 +327,11 @@ public class WrapperEntity implements Tickable { return onGround; } - public Vector3d getVelocity() { + public @NotNull Vector3d getVelocity() { return velocity; } - public void setVelocity(Vector3d velocity) { + public void setVelocity(@NotNull Vector3d velocity) { this.velocity = velocity; sendPacketToViewers(getVelocityPacket()); } @@ -331,7 +360,7 @@ public class WrapperEntity implements Tickable { return entityId; } - public EntityMeta getEntityMeta() { + public @NotNull EntityMeta getEntityMeta() { return entityMeta; } @@ -339,12 +368,12 @@ public class WrapperEntity implements Tickable { return metaClass.cast(entityMeta); } - public void consumeEntityMeta(@NotNull Class metaClass, Consumer consumer) { + public void consumeEntityMeta(@NotNull Class metaClass, @NotNull Consumer consumer) { T meta = getEntityMeta(metaClass); consumer.accept(meta); } - public void consumeMeta(Consumer consumer) { + public void consumeMeta(@NotNull Consumer consumer) { consumer.accept(entityMeta); } @@ -358,13 +387,14 @@ public class WrapperEntity implements Tickable { /** * Returns an unmodifiable set of the passengers of the entity. + * * @return the passengers of the entity */ - public Set getPassengers() { + public @NotNull Set getPassengers() { return Collections.unmodifiableSet(passengers); } - public WrapperEntity getRiding() { + public @Nullable WrapperEntity getRiding() { return EntityLib.getApi().getEntity(riding); } @@ -433,20 +463,22 @@ public class WrapperEntity implements Tickable { new WrapperPlayServerEntityRotation(entityId, yaw, pitch, onGround), new WrapperPlayServerEntityHeadLook(entityId, yaw) ); + this.location.setYaw(yaw); this.location.setPitch(pitch); } - public void rotateHead(Location location) { + public void rotateHead(@NotNull Location location) { rotateHead(location.getYaw(), location.getPitch()); } - public void rotateHead(WrapperEntity entity) { + public void rotateHead(@NotNull WrapperEntity entity) { rotateHead(entity.getLocation()); } public void refresh() { if (!spawned) return; + sendPacketsToViewers(entityMeta.createPacket(), createPassengerPacket()); } @@ -457,13 +489,12 @@ public class WrapperEntity implements Tickable { sendPacket(uuid, packet); sendPacket(uuid, new WrapperPlayServerBundle()); }); - } - else { + } else { viewers.forEach(uuid -> sendPacket(uuid, packet)); } } - public void sendPacketsToViewers(PacketWrapper... wrappers) { + public void sendPacketsToViewers(PacketWrapper @NotNull ... wrappers) { for (PacketWrapper wrapper : wrappers) { sendPacketToViewers(wrapper); } @@ -483,13 +514,21 @@ public class WrapperEntity implements Tickable { private static void sendPacket(UUID user, PacketWrapper wrapper) { if (wrapper == null) return; + Object channel = EntityLib.getApi().getPacketEvents().getProtocolManager().getChannel(user); if (channel == null) { if (EntityLib.getApi().getSettings().isDebugMode()) { EntityLib.getPlatform().getLogger().warning("Failed to send packet to " + user + " because the channel was null. They may be disconnected/not online."); } + return; } + + // Special handling for entity metadata packets to support `GlobalTranslator` functionality and component rendering + if (wrapper instanceof WrapperPlayServerEntityMetadata) { + PacketUtil.renderPacket(user, (WrapperPlayServerEntityMetadata) wrapper); + } + EntityLib.getApi().getPacketEvents().getProtocolManager().sendPacket(channel, wrapper); } @@ -504,12 +543,14 @@ public class WrapperEntity implements Tickable { /** * Adds a passenger to the entity. The passenger will be visible to all viewers of the entity. + * * @param passenger the entity id of the passenger */ public void addPassenger(int passenger) { if (passengers.contains(passenger)) { throw new IllegalArgumentException("Passenger already exists"); } + passengers.add(passenger); sendPacketToViewers(createPassengerPacket()); WrapperEntity e = EntityLib.getApi().getEntity(passenger); @@ -533,9 +574,10 @@ public class WrapperEntity implements Tickable { /** * Adds multiple passengers to the entity. The passengers will be visible to all viewers of the entity. + * * @param passengers the entity ids of the passengers */ - public void addPassengers(int... passengers) { + public void addPassengers(int @NotNull ... passengers) { for (int passenger : passengers) { addPassenger(passenger); } @@ -543,17 +585,19 @@ public class WrapperEntity implements Tickable { /** * Adds a passenger to the entity. The passenger will be visible to all viewers of the entity. + * * @param passenger the wrapper entity passenger */ - public void addPassenger(WrapperEntity passenger) { + public void addPassenger(@NotNull WrapperEntity passenger) { addPassenger(passenger.getEntityId()); } /** * Adds multiple passengers to the entity. The passengers will be visible to all viewers of the entity. + * * @param passengers the wrapper entity passengers */ - public void addPassengers(WrapperEntity... passengers) { + public void addPassengers(WrapperEntity @NotNull ... passengers) { for (WrapperEntity passenger : passengers) { addPassenger(passenger); } @@ -561,12 +605,14 @@ public class WrapperEntity implements Tickable { /** * Removes a passenger from the entity. The passenger will be removed from the view of all viewers of the entity. + * * @param passenger the entity id of the passenger */ public void removePassenger(int passenger) { if (!passengers.contains(passenger)) { throw new IllegalArgumentException("Passenger does not exist"); } + passengers.remove(passenger); sendPacketToViewers(createPassengerPacket()); WrapperEntity e = EntityLib.getApi().getEntity(passenger); @@ -588,15 +634,16 @@ public class WrapperEntity implements Tickable { * @param passenger the passenger wrapper entity * @return true if the entity has the passenger, false otherwise */ - public boolean hasPassenger(WrapperEntity passenger) { + public boolean hasPassenger(@NotNull WrapperEntity passenger) { return hasPassenger(passenger.getEntityId()); } /** * Removes multiple passengers from the entity. The passengers will be removed from the view of all viewers of the entity. + * * @param passengers the entity ids of the passengers */ - public void removePassengers(int... passengers) { + public void removePassengers(int @NotNull ... passengers) { for (int passenger : passengers) { removePassenger(passenger); } @@ -604,17 +651,19 @@ public class WrapperEntity implements Tickable { /** * Removes a passenger from the entity. The passenger will be removed from the view of all viewers of the entity. + * * @param passenger the wrapper entity passenger */ - public void removePassenger(WrapperEntity passenger) { + public void removePassenger(@NotNull WrapperEntity passenger) { removePassenger(passenger.getEntityId()); } /** * Removes multiple passengers from the entity. The passengers will be removed from the view of all viewers of the entity. + * * @param passengers the wrapper entity passengers */ - public void removePassengers(WrapperEntity... passengers) { + public void removePassengers(WrapperEntity @NotNull ... passengers) { for (WrapperEntity passenger : passengers) { removePassenger(passenger); } @@ -631,11 +680,11 @@ public class WrapperEntity implements Tickable { return Collections.unmodifiableSet(viewers); } - public boolean hasViewer(UUID uuid) { + public boolean hasViewer(@NotNull UUID uuid) { return viewers.contains(uuid); } - public boolean hasViewer(User user) { + public boolean hasViewer(@NotNull User user) { return hasViewer(user.getUUID()); } diff --git a/api/src/main/java/me/tofaa/entitylib/wrapper/WrapperEntityPotionEffect.java b/api/src/main/java/me/tofaa/entitylib/wrapper/WrapperEntityPotionEffect.java new file mode 100644 index 0000000..eecc52d --- /dev/null +++ b/api/src/main/java/me/tofaa/entitylib/wrapper/WrapperEntityPotionEffect.java @@ -0,0 +1,229 @@ +package me.tofaa.entitylib.wrapper; + +import com.github.retrooper.packetevents.protocol.nbt.NBTCompound; +import com.github.retrooper.packetevents.protocol.potion.PotionType; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityEffect; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerRemoveEntityEffect; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public class WrapperEntityPotionEffect { + private final WrapperLivingEntity entity; + private final Map effects = new ConcurrentHashMap<>(); + + private boolean notifyChanges = true; + + public WrapperEntityPotionEffect(WrapperLivingEntity entity) { + this.entity = entity; + } + + /** + * @param type The type of the potion effect {@link com.github.retrooper.packetevents.protocol.potion.PotionTypes} + * @param amplifier The amplifier of the potion effect. The notchian client displays the number as (amplifier + 1) + * @param duration The duration of the potion effect in ticks, if set to -1 the client will display the effect as infinite + * @param ambient A + * @param visible A + * @param showIcons A + * @param factorData The factor data of the potion effect, if hasFactorData is false this will be ignored + */ + public void addPotionEffect( + PotionType type, + int amplifier, + int duration, + boolean ambient, + boolean visible, + boolean showIcons, + @Nullable NBTCompound factorData + ) { + WrapperPotionEffect effect = new WrapperPotionEffect(type, amplifier, duration, ambient, visible, showIcons, factorData); + + this.effects.put(type, effect); + + this.entity.sendPacketToViewers(createEffectPacket(effect)); + } + + /** + * @param type The type of the potion effect {@link com.github.retrooper.packetevents.protocol.potion.PotionTypes} + * @param amplifier The amplifier of the potion effect. The notchian client displays the number as (amplifier + 1) + * @param duration The duration of the potion effect in ticks, if set to -1 the client will display the effect as infinite + * @param flags The bit flags of the potion effect see: https://minecraft.wiki/w/Java_Edition_protocol#Entity_Effect + * @param factorData The factor data of the potion effect, if hasFactorData is false this will be ignored + */ + public void addPotionEffect( + PotionType type, + int amplifier, + int duration, + byte flags, + @Nullable NBTCompound factorData + ) { + BitSet flagsBitSet = BitSet.valueOf(new byte[]{flags}); + + boolean ambient = flagsBitSet.get(0); + boolean visible = flagsBitSet.get(1); + boolean icons = flagsBitSet.get(2); + + addPotionEffect(type, amplifier, duration, ambient, visible, icons, factorData); + } + + /** + * @param type The type of the potion effect {@link com.github.retrooper.packetevents.protocol.potion.PotionTypes} + * @param amplifier The amplifier of the potion effect. The notchian client displays the number as (amplifier + 1) + * @param duration The duration of the potion effect in ticks, if set to -1 the client will display the effect as infinite + * @param flags The bit flags of the potion effect see: https://minecraft.wiki/w/Java_Edition_protocol#Entity_Effect + * @param hasFactorData Whether the potion effect has factor data + * @param factorData The factor data of the potion effect, if hasFactorData is false this will be ignored + */ + public void addPotionEffect( + PotionType type, + int amplifier, + int duration, + byte flags, + boolean hasFactorData, + @Nullable NBTCompound factorData + ) { + addPotionEffect(type, amplifier, duration, flags, hasFactorData ? factorData : null); + } + + /** + * Adds a potion effect to the entity. + * EntityLib will not keep track of the potions you give or what you do with them, + * this simply sends the packet to the viewers of the entity. + * + * @param type The type of the potion effect {@link com.github.retrooper.packetevents.protocol.potion.PotionTypes} + * @param amplifier The amplifier of the potion effect. The notchian client displays the number as (amplifier + 1) + * @param duration The duration of the potion effect in ticks, if set to -1 the client will display the effect as infinite + * @param flags The bit flags of the potion effect see: https://minecraft.wiki/w/Java_Edition_protocol#Entity_Effect + */ + public void addPotionEffect( + PotionType type, + int amplifier, + int duration, + byte flags + ) { + addPotionEffect(type, amplifier, duration, flags, false, null); + } + + public void removePotionEffect(@NotNull PotionType potionType) { + if (this.effects.remove(potionType) != null) { + this.entity.sendPacketsToViewers(createRemoveEffectPacket(potionType)); + } + } + + public void clearPotionEffects() { + new ArrayList<>(this.effects.keySet()).forEach(this::removePotionEffect); + } + + public @NotNull List createEffectPackets() { + List packets = new ArrayList<>(); + + this.effects.forEach((potionType, effect) -> packets.add(createEffectPacket(effect))); + + return packets; + } + + public @NotNull WrapperPlayServerEntityEffect createEffectPacket(@NotNull WrapperPotionEffect effect) { + PotionType potionType = effect.getPotionType(); + int amplifier = effect.getAmplifier(); + int duration = effect.getDuration(); + boolean ambient = effect.isAmbient(); + boolean visible = effect.isVisible(); + boolean icons = effect.hasIcons(); + NBTCompound factorData = effect.getFactorData(); + + int flags = 0; + + flags |= ambient ? 1 : 0; // Bit 0 para ambient + flags |= visible ? (1 << 1) : 0; // Bit 1 para visible + flags |= icons ? (1 << 2) : 0; // Bit 2 para icons + + WrapperPlayServerEntityEffect wrapperPlayServerEntityEffect = new WrapperPlayServerEntityEffect( + 0, + null, + 0, + 0, + (byte) flags + ); + + wrapperPlayServerEntityEffect.setEntityId(this.entity.getEntityId()); + wrapperPlayServerEntityEffect.setPotionType(potionType); + wrapperPlayServerEntityEffect.setEffectAmplifier(amplifier); + wrapperPlayServerEntityEffect.setEffectDurationTicks(duration); + wrapperPlayServerEntityEffect.setFactorData(factorData); + + return wrapperPlayServerEntityEffect; + } + + public @NotNull WrapperPlayServerRemoveEntityEffect createRemoveEffectPacket(@NotNull PotionType potionType) { + return new WrapperPlayServerRemoveEntityEffect(this.entity.getEntityId(), potionType); + } + + public void refresh() { + if (notifyChanges) { + new ArrayList<>(this.effects.values()).forEach(effect -> { + WrapperPlayServerEntityEffect wrapperPlayServerEntityEffect = createEffectPacket(effect); + + this.entity.sendPacketToViewers(wrapperPlayServerEntityEffect); + }); + } + } + + public boolean isNotifyingChanges() { + return notifyChanges; + } + + public void setNotifyChanges(boolean notifyChanges) { + this.notifyChanges = notifyChanges; + refresh(); + } + + public static class WrapperPotionEffect { + private final PotionType potionType; + private final int amplifier; + private final int duration; + private final boolean ambient; + private final boolean visible; + private final boolean icons; + private final @Nullable NBTCompound factorData; + + private WrapperPotionEffect(PotionType potionType, int amplifier, int duration, boolean ambient, boolean visible, boolean icons, @Nullable NBTCompound factorData) { + this.potionType = potionType; + this.amplifier = amplifier; + this.duration = duration; + this.ambient = ambient; + this.visible = visible; + this.icons = icons; + this.factorData = factorData; + } + + public PotionType getPotionType() { + return potionType; + } + + public int getAmplifier() { + return amplifier; + } + + public int getDuration() { + return duration; + } + + public boolean isAmbient() { + return ambient; + } + + public boolean isVisible() { + return visible; + } + + public boolean hasIcons() { + return icons; + } + + public @Nullable NBTCompound getFactorData() { + return factorData; + } + } +} diff --git a/api/src/main/java/me/tofaa/entitylib/wrapper/WrapperLivingEntity.java b/api/src/main/java/me/tofaa/entitylib/wrapper/WrapperLivingEntity.java index df599ce..290e557 100644 --- a/api/src/main/java/me/tofaa/entitylib/wrapper/WrapperLivingEntity.java +++ b/api/src/main/java/me/tofaa/entitylib/wrapper/WrapperLivingEntity.java @@ -1,25 +1,37 @@ package me.tofaa.entitylib.wrapper; +import com.github.retrooper.packetevents.manager.server.ServerVersion; import com.github.retrooper.packetevents.protocol.entity.type.EntityType; import com.github.retrooper.packetevents.protocol.nbt.NBTCompound; import com.github.retrooper.packetevents.protocol.potion.PotionType; +import com.github.retrooper.packetevents.protocol.world.Location; +import com.github.retrooper.packetevents.wrapper.PacketWrapper; import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityAnimation; import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityEffect; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityEquipment; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerHurtAnimation; import me.tofaa.entitylib.EntityLib; +import me.tofaa.entitylib.container.EntityContainer; import me.tofaa.entitylib.meta.EntityMeta; +import me.tofaa.entitylib.utils.VersionUtil; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; -public class WrapperLivingEntity extends WrapperEntity{ - +public class WrapperLivingEntity extends WrapperEntity { private final WrapperEntityEquipment equipment; private final WrapperEntityAttributes attributes; + private final WrapperEntityPotionEffect potionEffect; public WrapperLivingEntity(int entityId, UUID uuid, EntityType entityType, EntityMeta entityMeta) { super(entityId, uuid, entityType, entityMeta); + this.equipment = new WrapperEntityEquipment(this); this.attributes = new WrapperEntityAttributes(this); + this.potionEffect = new WrapperEntityPotionEffect(this); } public WrapperLivingEntity(int entityId, UUID uuid, EntityType entityType) { @@ -33,6 +45,7 @@ public class WrapperLivingEntity extends WrapperEntity{ public WrapperLivingEntity(UUID uuid, EntityType entityType) { this(EntityLib.getPlatform().getEntityIdProvider().provide(uuid, entityType), uuid, entityType); } + public WrapperLivingEntity(EntityType entityType) { this(EntityLib.getPlatform().getEntityUuidProvider().provide(entityType), entityType); } @@ -40,60 +53,74 @@ public class WrapperLivingEntity extends WrapperEntity{ @Override public void refresh() { super.refresh(); - equipment.refresh(); - attributes.refresh(); + + this.equipment.refresh(); + this.potionEffect.refresh(); + this.attributes.refresh(); } public WrapperEntityAttributes getAttributes() { - return attributes; + return this.attributes; } + public WrapperEntityEquipment getEquipment() { + return this.equipment; + } + public WrapperEntityPotionEffect getPotionEffect() { + return this.potionEffect; + } + + public @NotNull List> createSpawnPackets() { + List> packets = new ArrayList<>(); + + packets.add(getAttributes().createPacket()); + packets.add(getEquipment().createPacket()); + packets.addAll(getPotionEffect().createEffectPackets()); + + return packets; + } /** * Adds a potion effect to the entity. * EntityLib will not keep track of the potions you give or what you do with them, * this simply sends the packet to the viewers of the entity. - * @param type The type of the potion effect {@link com.github.retrooper.packetevents.protocol.potion.PotionTypes} - * @param amplifier The amplifier of the potion effect. The notchian client displays the number as (amplifier + 1) - * @param duration The duration of the potion effect in ticks, if set to -1 the client will display the effect as infinite - * @param flags The bit flags of the potion effect see: https://wiki.vg/Protocol#Entity_Effect + * + * @param type The type of the potion effect {@link com.github.retrooper.packetevents.protocol.potion.PotionTypes} + * @param amplifier The amplifier of the potion effect. The notchian client displays the number as (amplifier + 1) + * @param duration The duration of the potion effect in ticks, if set to -1 the client will display the effect as infinite + * @param flags The bit flags of the potion effect see: + * https://minecraft.wiki/w/Java_Edition_protocol#Entity_Effect * @param hasFactorData Whether the potion effect has factor data - * @param factorData The factor data of the potion effect, if hasFactorData is false this will be ignored + * @param factorData The factor data of the potion effect, if hasFactorData is false this will be ignored */ public void addPotionEffect( - PotionType type, - int amplifier, - int duration, - byte flags, - boolean hasFactorData, - @Nullable NBTCompound factorData + PotionType type, + int amplifier, + int duration, + byte flags, + boolean hasFactorData, + @Nullable NBTCompound factorData ) { - sendPacketToViewers( - new WrapperPlayServerEntityEffect( - getEntityId(), - type, - amplifier, - duration, - flags - ) - ); + this.potionEffect.addPotionEffect(type, amplifier, duration, flags, hasFactorData, factorData); } /** * Adds a potion effect to the entity. * EntityLib will not keep track of the potions you give or what you do with them, * this simply sends the packet to the viewers of the entity. - * @param type The type of the potion effect {@link com.github.retrooper.packetevents.protocol.potion.PotionTypes} + * + * @param type The type of the potion effect {@link com.github.retrooper.packetevents.protocol.potion.PotionTypes} * @param amplifier The amplifier of the potion effect. The notchian client displays the number as (amplifier + 1) - * @param duration The duration of the potion effect in ticks, if set to -1 the client will display the effect as infinite - * @param flags The bit flags of the potion effect see: https://wiki.vg/Protocol#Entity_Effect + * @param duration The duration of the potion effect in ticks, if set to -1 the client will display the effect as infinite + * @param flags The bit flags of the potion effect see: + * https://minecraft.wiki/w/Java_Edition_protocol#Entity_Effect */ public void addPotionEffect( - PotionType type, - int amplifier, - int duration, - byte flags + PotionType type, + int amplifier, + int duration, + byte flags ) { addPotionEffect(type, amplifier, duration, flags, false, null); } @@ -110,7 +137,30 @@ public class WrapperLivingEntity extends WrapperEntity{ sendAnimation(WrapperPlayServerEntityAnimation.EntityAnimationType.WAKE_UP); } + /** + * Plays the hurt animation of the entity. + * This method is deprecated and should use {@link #playHurtAnimation(int)} instead. + */ + @Deprecated public void playHurtAnimation() { + playHurtAnimation(0); + } + + /** + * Plays the hurt animation of the entity. + * + * @param yaw The yaw of the entity when the hurt animation is played. + * For any entity other than a player it's safe to simply put 0, as it's not used. + * The yaw is only used on 1.19.4+. + */ + public void playHurtAnimation(int yaw) { + // 1.19.4+ uses a different packet for hurt animation than previous versions + if (VersionUtil.isNewerThan(ServerVersion.V_1_19_4)) { + sendPacketToViewers( + new WrapperPlayServerHurtAnimation(getEntityId(), yaw) + ); + return; + } sendAnimation(WrapperPlayServerEntityAnimation.EntityAnimationType.HURT); } @@ -127,8 +177,4 @@ public class WrapperLivingEntity extends WrapperEntity{ new WrapperPlayServerEntityAnimation(getEntityId(), type) ); } - - public WrapperEntityEquipment getEquipment() { - return equipment; - } } diff --git a/api/src/main/java/me/tofaa/entitylib/wrapper/WrapperPlayer.java b/api/src/main/java/me/tofaa/entitylib/wrapper/WrapperPlayer.java index ad4e0cd..d50a91b 100644 --- a/api/src/main/java/me/tofaa/entitylib/wrapper/WrapperPlayer.java +++ b/api/src/main/java/me/tofaa/entitylib/wrapper/WrapperPlayer.java @@ -1,16 +1,24 @@ package me.tofaa.entitylib.wrapper; +import com.github.retrooper.packetevents.manager.server.ServerVersion; import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes; -import com.github.retrooper.packetevents.protocol.player.*; -import com.github.retrooper.packetevents.protocol.world.Location; -import com.github.retrooper.packetevents.util.Vector3d; +import com.github.retrooper.packetevents.protocol.player.GameMode; +import com.github.retrooper.packetevents.protocol.player.TextureProperty; +import com.github.retrooper.packetevents.protocol.player.UserProfile; import com.github.retrooper.packetevents.wrapper.PacketWrapper; -import com.github.retrooper.packetevents.wrapper.play.server.*; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerDestroyEntities; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityHeadLook; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityRotation; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerPlayerInfoRemove; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerPlayerInfoUpdate; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerSpawnPlayer; import me.tofaa.entitylib.EntityLib; import me.tofaa.entitylib.meta.EntityMeta; +import java.util.EnumSet; +import java.util.List; +import java.util.UUID; import net.kyori.adventure.text.Component; - -import java.util.*; +import org.jetbrains.annotations.NotNull; public class WrapperPlayer extends WrapperLivingEntity { @@ -25,6 +33,27 @@ public class WrapperPlayer extends WrapperLivingEntity { this.profile = profile; } + @Override + protected PacketWrapper createSpawnPacket() { + if (EntityLib.getApi().getPacketEvents().getServerManager().getVersion().isOlderThanOrEquals(ServerVersion.V_1_20_1)) { + return new WrapperPlayServerSpawnPlayer( + getEntityId(), + profile.getUUID(), + getLocation(), + getEntityMeta().entityData() + ); + } + return super.createSpawnPacket(); + } + + @Override + public @NotNull List> createSpawnPackets() { + final List> packets = super.createSpawnPackets(); + packets.add(new WrapperPlayServerEntityRotation(getEntityId(), getYaw(), getPitch(), isOnGround())); + packets.add(new WrapperPlayServerEntityHeadLook(getEntityId(), getYaw())); + return packets; + } + public WrapperPlayServerPlayerInfoUpdate tabListPacket() { EnumSet actions = EnumSet.of( WrapperPlayServerPlayerInfoUpdate.Action.ADD_PLAYER, diff --git a/buildSrc/src/main/kotlin/me/tofaa/entitylib/version/ELVersionTask.kt b/buildSrc/src/main/kotlin/me/tofaa/entitylib/version/ELVersionTask.kt index c01ef12..1606cc1 100644 --- a/buildSrc/src/main/kotlin/me/tofaa/entitylib/version/ELVersionTask.kt +++ b/buildSrc/src/main/kotlin/me/tofaa/entitylib/version/ELVersionTask.kt @@ -40,7 +40,8 @@ abstract class ELVersionTask : DefaultTask() { * This file is generated by the auto-version task. Modifying it will have no effect. */ package $packageName; - + + import java.text.DateFormat; import com.github.retrooper.packetevents.util.PEVersion; public final class ELVersions { @@ -52,6 +53,29 @@ abstract class ELVersionTask : DefaultTask() { private ELVersions() { throw new IllegalStateException(); } + + public static class Version { + + private final long timestamp; + + public Version(long timestamp) { + this.timestamp = timestamp; + } + + public long getTimestamp() { + return timestamp; + } + + public String getTimestampFormatted() { + return DateFormat.getDateTimeInstance().format(new java.util.Date(timestamp)); + } + + public boolean isOlderThan(Version version) { + return this.timestamp < version.timestamp; + } + + } + } """.trimIndent()) } diff --git a/common/src/main/java/me/tofaa/entitylib/common/AbstractEntityLibAPI.java b/common/src/main/java/me/tofaa/entitylib/common/AbstractEntityLibAPI.java index 5bb4f54..dd2922b 100644 --- a/common/src/main/java/me/tofaa/entitylib/common/AbstractEntityLibAPI.java +++ b/common/src/main/java/me/tofaa/entitylib/common/AbstractEntityLibAPI.java @@ -4,17 +4,19 @@ import com.github.retrooper.packetevents.PacketEventsAPI; import me.tofaa.entitylib.APIConfig; import me.tofaa.entitylib.EntityLibAPI; import me.tofaa.entitylib.Platform; +import me.tofaa.entitylib.UserLocaleProvider; import me.tofaa.entitylib.container.EntityContainer; import me.tofaa.entitylib.tick.TickContainer; import me.tofaa.entitylib.wrapper.WrapperEntity; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; - +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.UUID; public abstract class AbstractEntityLibAPI implements EntityLibAPI { - protected final Platform

platform; protected final PacketEventsAPI packetEvents; protected final APIConfig settings; @@ -36,7 +38,6 @@ public abstract class AbstractEntityLibAPI implements EntityLibAPI { @Override public @Nullable WrapperEntity getEntity(@NotNull UUID uuid) { return defaultEntityContainer.getEntity(uuid); - } @Override @@ -49,9 +50,8 @@ public abstract class AbstractEntityLibAPI implements EntityLibAPI { return defaultEntityContainer; } - @NotNull @Override - public APIConfig getSettings() { + public @NotNull APIConfig getSettings() { return settings; } @@ -60,9 +60,13 @@ public abstract class AbstractEntityLibAPI implements EntityLibAPI { return packetEvents; } - @NotNull @Override - public Collection> getTickContainers() { + public @NotNull Collection> getTickContainers() { return tickContainers; } + + @Override + public @NotNull UserLocaleProvider getUserLocaleProvider() { + return platform.getUserLocaleProvider(); + } } diff --git a/libs.versions.toml b/libs.versions.toml index 4b2c87a..7b89472 100644 --- a/libs.versions.toml +++ b/libs.versions.toml @@ -1,8 +1,8 @@ [versions] -adventure = "4.16.0" -jetbrains-annotations = "24.0.0" -gson = "2.11.0" -packetevents = "2.7.0" +adventure = "4.22.0" +jetbrains-annotations = "26.0.2" +gson = "2.13.1" +packetevents = "2.9.1" paper = "1.21-R0.1-SNAPSHOT" velocity = "3.3.0-SNAPSHOT" run-paper = "2.3.0" diff --git a/platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityLibPlatform.java b/platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityLibPlatform.java index ffa5640..34c7a7d 100644 --- a/platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityLibPlatform.java +++ b/platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotEntityLibPlatform.java @@ -1,15 +1,21 @@ package me.tofaa.entitylib.spigot; +import com.github.retrooper.packetevents.PacketEventsAPI; +import io.github.retrooper.packetevents.bstats.bukkit.Metrics; +import io.github.retrooper.packetevents.bstats.charts.SimplePie; import me.tofaa.entitylib.APIConfig; +import me.tofaa.entitylib.EntityLib; +import me.tofaa.entitylib.UserLocaleProvider; import me.tofaa.entitylib.common.AbstractPlatform; +import java.util.logging.Logger; +import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; -import java.util.logging.Logger; - public class SpigotEntityLibPlatform extends AbstractPlatform { private SpigotEntityLibAPI api; + private UserLocaleProvider userLocaleProvider = new SpigotPlayerLocaleProvider(); public SpigotEntityLibPlatform(@NotNull JavaPlugin plugin) { super(plugin); @@ -23,6 +29,12 @@ public class SpigotEntityLibPlatform extends AbstractPlatform { this.setEntityIdProvider(new SpigotEntityIdProvider(this)); this.api.onLoad(); this.api.onEnable(); + if (settings.shouldUseBstats()) { + PacketEventsAPI pe = (PacketEventsAPI) api.getPacketEvents(); + Metrics metrics = new Metrics(pe.getPlugin(), 21916); + metrics.addCustomChart(new SimplePie("entitylib-version", () -> EntityLib.getVersion().toString())); + } + } @Override @@ -34,4 +46,14 @@ public class SpigotEntityLibPlatform extends AbstractPlatform { public String getName() { return "Spigot"; } + + @Override + public @NotNull UserLocaleProvider getUserLocaleProvider() { + return userLocaleProvider; + } + + @Override + public void setUserLocaleProvider(@NotNull final UserLocaleProvider provider) { + this.userLocaleProvider = provider; + } } diff --git a/platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotPlayerLocaleProvider.java b/platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotPlayerLocaleProvider.java new file mode 100644 index 0000000..a6d30ac --- /dev/null +++ b/platforms/spigot/src/main/java/me/tofaa/entitylib/spigot/SpigotPlayerLocaleProvider.java @@ -0,0 +1,73 @@ +package me.tofaa.entitylib.spigot; + +import me.tofaa.entitylib.UserLocaleProvider; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.Locale; +import java.util.UUID; +import java.util.function.Function; +import net.kyori.adventure.translation.Translator; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + + +/** + * This implementation is based on code from the scoreboard-library project: + * LocaleProvider + * Modified and adapted for use in EntityLib. + */ +public class SpigotPlayerLocaleProvider implements UserLocaleProvider { + private static final Locale DEFAULT_LOCALE = Locale.US; + private static final Function provider = get(); + + @Override + public Locale locale(final UUID user) { + final Player player = Bukkit.getPlayer(user); + return player == null ? DEFAULT_LOCALE : provider.apply(player); + } + + private static @NotNull Function get() { + MethodHandles.Lookup lookup = MethodHandles.publicLookup(); + try { + MethodHandle adventureMethod = lookup.findVirtual(Player.class, "locale", MethodType.methodType(Locale.class)); + return player -> { + try { + return (Locale) adventureMethod.invokeExact(player); + } catch (Throwable e) { + throw new RuntimeException(e); + } + }; + } catch (IllegalAccessException | NoSuchMethodException ignored) { + } + + MethodType methodType = MethodType.methodType(String.class); + try { + MethodHandle legacySpigotMethod = lookup.findVirtual(Player.Spigot.class, "getLocale", methodType); + return player -> { + try { + Locale locale = Translator.parseLocale((String) legacySpigotMethod.invokeExact(player.spigot())); + return locale == null ? DEFAULT_LOCALE : locale; + } catch (Throwable e) { + throw new RuntimeException(e); + } + }; + } catch (IllegalAccessException | NoSuchMethodException ignored) { + } + + try { + MethodHandle legacyMethod = lookup.findVirtual(Player.class, "getLocale", methodType); + return player -> { + try { + Locale locale = Translator.parseLocale((String) legacyMethod.invokeExact(player)); + return locale == null ? DEFAULT_LOCALE : locale; + } catch (Throwable e) { + throw new RuntimeException(e); + } + }; + } catch (IllegalAccessException | NoSuchMethodException ignored) { + throw new RuntimeException("No way to get players locale found"); + } + } +} diff --git a/platforms/standalone/src/main/java/me/tofaa/entitylib/standalone/StandaloneEntityLibPlatform.java b/platforms/standalone/src/main/java/me/tofaa/entitylib/standalone/StandaloneEntityLibPlatform.java index f344e45..b26e2e1 100644 --- a/platforms/standalone/src/main/java/me/tofaa/entitylib/standalone/StandaloneEntityLibPlatform.java +++ b/platforms/standalone/src/main/java/me/tofaa/entitylib/standalone/StandaloneEntityLibPlatform.java @@ -2,15 +2,18 @@ package me.tofaa.entitylib.standalone; import me.tofaa.entitylib.APIConfig; import me.tofaa.entitylib.EntityLibAPI; +import me.tofaa.entitylib.UserLocaleProvider; import me.tofaa.entitylib.common.AbstractPlatform; +import java.util.Locale; import org.jetbrains.annotations.NotNull; public class StandaloneEntityLibPlatform extends AbstractPlatform { private StandaloneEntityLibApi api; + private UserLocaleProvider userLocaleProvider = (user) -> Locale.US; - private StandaloneEntityLibPlatform() { + public StandaloneEntityLibPlatform() { super(null); } @@ -34,4 +37,14 @@ public class StandaloneEntityLibPlatform extends AbstractPlatform { public String getName() { return "Standalone"; } + + @Override + public @NotNull UserLocaleProvider getUserLocaleProvider() { + return userLocaleProvider; + } + + @Override + public void setUserLocaleProvider(@NotNull final UserLocaleProvider provider) { + this.userLocaleProvider = provider; + } } diff --git a/platforms/velocity/src/main/java/me/tofaa/entitylib/velocity/VelocityEntityLibPlatform.java b/platforms/velocity/src/main/java/me/tofaa/entitylib/velocity/VelocityEntityLibPlatform.java index 815441c..b0e96a9 100644 --- a/platforms/velocity/src/main/java/me/tofaa/entitylib/velocity/VelocityEntityLibPlatform.java +++ b/platforms/velocity/src/main/java/me/tofaa/entitylib/velocity/VelocityEntityLibPlatform.java @@ -9,6 +9,7 @@ import com.velocitypowered.api.proxy.ProxyServer; import io.github.retrooper.packetevents.velocity.factory.VelocityPacketEventsBuilder; import me.tofaa.entitylib.APIConfig; import me.tofaa.entitylib.EntityLibAPI; +import me.tofaa.entitylib.UserLocaleProvider; import me.tofaa.entitylib.common.AbstractPlatform; import org.jetbrains.annotations.NotNull; @@ -16,6 +17,7 @@ import java.util.logging.Logger; public class VelocityEntityLibPlatform extends AbstractPlatform { private VelocityEntityLibAPI api; + private UserLocaleProvider userLocaleProvider; private Object plugin; public VelocityEntityLibPlatform(Object plugin, ProxyServer handle) { @@ -54,4 +56,14 @@ public class VelocityEntityLibPlatform extends AbstractPlatform { public String getName() { return "Velocity"; } + + @Override + public @NotNull UserLocaleProvider getUserLocaleProvider() { + return userLocaleProvider; + } + + @Override + public void setUserLocaleProvider(final UserLocaleProvider userLocaleProvider) { + this.userLocaleProvider = userLocaleProvider; + } } diff --git a/platforms/velocity/src/main/java/me/tofaa/entitylib/velocity/VelocityPlayerLocaleProvider.java b/platforms/velocity/src/main/java/me/tofaa/entitylib/velocity/VelocityPlayerLocaleProvider.java new file mode 100644 index 0000000..5d9268a --- /dev/null +++ b/platforms/velocity/src/main/java/me/tofaa/entitylib/velocity/VelocityPlayerLocaleProvider.java @@ -0,0 +1,20 @@ +package me.tofaa.entitylib.velocity; + +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ProxyServer; +import me.tofaa.entitylib.UserLocaleProvider; +import java.util.Locale; +import java.util.UUID; + +public class VelocityPlayerLocaleProvider implements UserLocaleProvider { + private final ProxyServer proxyServer; + + public VelocityPlayerLocaleProvider(final ProxyServer proxyServer) { + this.proxyServer = proxyServer; + } + + @Override + public Locale locale(final UUID user) { + return proxyServer.getPlayer(user).map(Player::getEffectiveLocale).orElse(Locale.US); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index bf53135..b6f773a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -34,7 +34,7 @@ include(":platforms:spigot") include(":platforms:velocity") include(":platforms:standalone") -if (!System.getenv("JITPACK").toBoolean()) { +if (System.getenv("PRIVATE").toBoolean()) { include("discord-bot") include(":code-gen") include(":test-plugin") diff --git a/test-plugin/src/main/java/me/tofaa/testentitylib/TestEntityLibPlugin.java b/test-plugin/src/main/java/me/tofaa/testentitylib/TestEntityLibPlugin.java index a70b9c6..90af2e5 100644 --- a/test-plugin/src/main/java/me/tofaa/testentitylib/TestEntityLibPlugin.java +++ b/test-plugin/src/main/java/me/tofaa/testentitylib/TestEntityLibPlugin.java @@ -9,11 +9,13 @@ import org.bukkit.command.CommandMap; import org.bukkit.plugin.java.JavaPlugin; import java.lang.reflect.InvocationTargetException; +import java.text.DateFormat; public class TestEntityLibPlugin extends JavaPlugin { @Override public void onEnable() { + DateFormat.getDateTimeInstance().format(new java.util.Date(timestamp)); SpigotEntityLibPlatform platform = new SpigotEntityLibPlatform(this); APIConfig settings = new APIConfig(PacketEvents.getAPI())