diff --git a/build.gradle.kts b/build.gradle.kts index d9df0f1..b2c4df3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -47,8 +47,14 @@ tasks { archiveFileName = "${rootProject.name}-${ext["versionNoHash"]}.jar" archiveClassifier = null - relocate("net.kyori.adventure.text.serializer.gson", "io.github.retrooper.packetevents.adventure.serializer.gson") - relocate("net.kyori.adventure.text.serializer.legacy", "io.github.retrooper.packetevents.adventure.serializer.legacy") + relocate( + "net.kyori.adventure.text.serializer.gson", + "io.github.retrooper.packetevents.adventure.serializer.gson" + ) + relocate( + "net.kyori.adventure.text.serializer.legacy", + "io.github.retrooper.packetevents.adventure.serializer.legacy" + ) } assemble { diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 440e95a..02a2bbf 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -10,6 +10,7 @@ dependencies { compileOnlyApi(libs.bundles.adventure) compileOnlyApi(libs.snakeyaml) compileOnlyApi(libs.lombok) + compileOnly(libs.guava) annotationProcessor(libs.lombok) } diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/AHIPlatform.java b/common/src/main/java/com/deathmotion/antihealthindicator/AHIPlatform.java index 80e0e75..d0a13aa 100644 --- a/common/src/main/java/com/deathmotion/antihealthindicator/AHIPlatform.java +++ b/common/src/main/java/com/deathmotion/antihealthindicator/AHIPlatform.java @@ -21,7 +21,12 @@ import com.deathmotion.antihealthindicator.api.AntiHealthIndicator; import com.deathmotion.antihealthindicator.commands.AntiHealthIndicatorCommand; import com.deathmotion.antihealthindicator.interfaces.Scheduler; -import com.deathmotion.antihealthindicator.managers.*; +import com.deathmotion.antihealthindicator.managers.ConfigManager; +import com.deathmotion.antihealthindicator.managers.LogManager; +import com.deathmotion.antihealthindicator.managers.PlayerDataManager; +import com.deathmotion.antihealthindicator.packets.PacketPlayerJoinQuit; +import com.deathmotion.antihealthindicator.packets.SpoofManagerPacketListener; +import com.deathmotion.antihealthindicator.util.UpdateChecker; import com.github.retrooper.packetevents.PacketEvents; import lombok.Getter; import net.kyori.adventure.text.Component; @@ -31,14 +36,22 @@ @Getter public abstract class AHIPlatform

{ + + @Getter + private static AHIPlatform instance; + protected ConfigManager

configManager; protected LogManager

logManager; protected Scheduler scheduler; protected AntiHealthIndicatorCommand

command; - private CacheManager

cacheManager; + protected PlayerDataManager

playerDataManager; + + private UpdateChecker

updateChecker; public void commonOnInitialize() { + instance = this; + logManager = new LogManager<>(this); configManager = new ConfigManager<>(this); AntiHealthIndicator.setAPI(new AntiHealthIndicatorAPIImpl<>(this)); @@ -48,11 +61,13 @@ public void commonOnInitialize() { * Called when the platform is enabled. */ public void commonOnEnable() { - cacheManager = new CacheManager<>(this); command = new AntiHealthIndicatorCommand<>(this); + playerDataManager = new PlayerDataManager<>(this); + + PacketEvents.getAPI().getEventManager().registerListener(new PacketPlayerJoinQuit<>(this)); + PacketEvents.getAPI().getEventManager().registerListener(new SpoofManagerPacketListener<>(this)); - new UpdateManager<>(this); - new PacketManager<>(this); + this.updateChecker = new UpdateChecker<>(this); } /** diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/cache/EntityCache.java b/common/src/main/java/com/deathmotion/antihealthindicator/cache/EntityCache.java new file mode 100644 index 0000000..a507fa7 --- /dev/null +++ b/common/src/main/java/com/deathmotion/antihealthindicator/cache/EntityCache.java @@ -0,0 +1,98 @@ +/* + * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator + * Copyright (C) 2025 Bram and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.deathmotion.antihealthindicator.cache; + +import com.deathmotion.antihealthindicator.cache.entities.CachedEntity; +import com.deathmotion.antihealthindicator.cache.entities.RidableEntity; +import com.deathmotion.antihealthindicator.cache.trackers.EntityTracker; +import com.deathmotion.antihealthindicator.cache.trackers.VehicleTracker; +import com.deathmotion.antihealthindicator.data.AHIPlayer; +import com.github.retrooper.packetevents.event.PacketSendEvent; +import lombok.Getter; +import lombok.NonNull; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +@Getter +public class EntityCache { + private final AHIPlayer player; + private final ConcurrentHashMap cache; + private final EntityTracker entityTracker; + private final VehicleTracker vehicleTracker; + + public EntityCache(AHIPlayer player) { + this.player = player; + this.cache = new ConcurrentHashMap<>(); + this.entityTracker = new EntityTracker(player, this); + this.vehicleTracker = new VehicleTracker(player, this); + } + + public void onPacketSend(PacketSendEvent event) { + entityTracker.onPacketSend(event); + vehicleTracker.onPacketSend(event); + } + + public Optional getCachedEntity(int entityId) { + return Optional.ofNullable(cache.get(entityId)); + } + + public Optional getVehicleData(int entityId) { + return getCachedEntity(entityId) + .filter(entityData -> entityData instanceof RidableEntity) + .map(entityData -> (RidableEntity) entityData); + } + + public void addLivingEntity(int entityId, @NonNull CachedEntity cachedEntity) { + cache.put(entityId, cachedEntity); + } + + public void removeEntity(int entityId) { + cache.remove(entityId); + } + + public void resetUserCache() { + cache.clear(); + } + + public void updateVehiclePassenger(int entityId, int passengerId) { + getVehicleData(entityId).ifPresent(ridableEntityData -> ridableEntityData.setPassengerId(passengerId)); + } + + public float getVehicleHealth(int entityId) { + return getVehicleData(entityId).map(RidableEntity::getHealth).orElse(0.5f); + } + + public boolean isUserPassenger(int entityId) { + return getVehicleData(entityId).map(ridableEntityData -> ridableEntityData.getPassengerId() == player.user.getEntityId()).orElse(false); + } + + public int getPassengerId(int entityId) { + return getVehicleData(entityId).map(RidableEntity::getPassengerId).orElse(0); + } + + public int getEntityIdByPassengerId(int passengerId) { + return cache.entrySet().stream() + .filter(entry -> entry.getValue() instanceof RidableEntity && ((RidableEntity) entry.getValue()).getPassengerId() == passengerId) + .map(Map.Entry::getKey) + .findFirst() + .orElse(0); + } +} \ No newline at end of file diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/cache/entities/CachedEntity.java b/common/src/main/java/com/deathmotion/antihealthindicator/cache/entities/CachedEntity.java new file mode 100644 index 0000000..ce417c9 --- /dev/null +++ b/common/src/main/java/com/deathmotion/antihealthindicator/cache/entities/CachedEntity.java @@ -0,0 +1,34 @@ +/* + * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator + * Copyright (C) 2025 Bram and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.deathmotion.antihealthindicator.cache.entities; + +import com.deathmotion.antihealthindicator.data.AHIPlayer; +import com.github.retrooper.packetevents.protocol.entity.data.EntityData; +import com.github.retrooper.packetevents.protocol.entity.type.EntityType; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class CachedEntity { + private EntityType entityType; + + public void processMetaData(EntityData metaData, AHIPlayer player) { + } +} \ No newline at end of file diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/cache/entities/RidableEntity.java b/common/src/main/java/com/deathmotion/antihealthindicator/cache/entities/RidableEntity.java new file mode 100644 index 0000000..cec2af6 --- /dev/null +++ b/common/src/main/java/com/deathmotion/antihealthindicator/cache/entities/RidableEntity.java @@ -0,0 +1,38 @@ +/* + * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator + * Copyright (C) 2025 Bram and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.deathmotion.antihealthindicator.cache.entities; + +import com.deathmotion.antihealthindicator.data.AHIPlayer; +import com.github.retrooper.packetevents.protocol.entity.data.EntityData; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class RidableEntity extends CachedEntity { + private float health; + private int passengerId; + + @Override + public void processMetaData(EntityData metaData, AHIPlayer player) { + if (metaData.getIndex() == player.metadataIndex.HEALTH) { + setHealth((float) metaData.getValue()); + } + } +} \ No newline at end of file diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/cache/entities/WolfEntity.java b/common/src/main/java/com/deathmotion/antihealthindicator/cache/entities/WolfEntity.java new file mode 100644 index 0000000..9ce9c8d --- /dev/null +++ b/common/src/main/java/com/deathmotion/antihealthindicator/cache/entities/WolfEntity.java @@ -0,0 +1,67 @@ +/* + * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator + * Copyright (C) 2025 Bram and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.deathmotion.antihealthindicator.cache.entities; + +import com.deathmotion.antihealthindicator.data.AHIPlayer; +import com.github.retrooper.packetevents.protocol.entity.data.EntityData; +import lombok.Getter; +import lombok.Setter; + +import java.util.Optional; +import java.util.UUID; + +@Getter +@Setter +public class WolfEntity extends CachedEntity { + private boolean isTamed; + private UUID ownerUUID; + + public boolean isOwnerPresent() { + return ownerUUID != null; + } + + public UUID getOwnerUUID() { + if (!isOwnerPresent()) { + throw new IllegalStateException("Owner UUID not present"); + } + return ownerUUID; + } + + @Override + public void processMetaData(EntityData metaData, AHIPlayer player) { + int index = metaData.getIndex(); + + if (index == player.metadataIndex.TAMABLE_TAMED) { + setTamed(((Byte) metaData.getValue() & 0x04) != 0); + } else if (index == player.metadataIndex.TAMABLE_OWNER) { + Object value = metaData.getValue(); + + UUID ownerUUID = value instanceof String + ? Optional.of((String) value) + .filter(player.uuid.toString()::equals) + .map(UUID::fromString) + .orElse(null) + : ((Optional) value) + .filter(player.uuid::equals) + .orElse(null); + + setOwnerUUID(ownerUUID); + } + } +} diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/packetlisteners/EntityTracker.java b/common/src/main/java/com/deathmotion/antihealthindicator/cache/trackers/EntityTracker.java similarity index 53% rename from common/src/main/java/com/deathmotion/antihealthindicator/packetlisteners/EntityTracker.java rename to common/src/main/java/com/deathmotion/antihealthindicator/cache/trackers/EntityTracker.java index 99bd8f1..8119dda 100644 --- a/common/src/main/java/com/deathmotion/antihealthindicator/packetlisteners/EntityTracker.java +++ b/common/src/main/java/com/deathmotion/antihealthindicator/cache/trackers/EntityTracker.java @@ -1,34 +1,33 @@ /* - * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator - * Copyright (C) 2025 Bram and contributors + * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator + * Copyright (C) 2025 Bram and contributors * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * 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 General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ -package com.deathmotion.antihealthindicator.packetlisteners; +package com.deathmotion.antihealthindicator.cache.trackers; import com.deathmotion.antihealthindicator.AHIPlatform; +import com.deathmotion.antihealthindicator.cache.EntityCache; +import com.deathmotion.antihealthindicator.cache.entities.CachedEntity; +import com.deathmotion.antihealthindicator.cache.entities.RidableEntity; +import com.deathmotion.antihealthindicator.cache.entities.WolfEntity; +import com.deathmotion.antihealthindicator.data.AHIPlayer; import com.deathmotion.antihealthindicator.data.RidableEntities; import com.deathmotion.antihealthindicator.data.Settings; -import com.deathmotion.antihealthindicator.data.cache.CachedEntity; -import com.deathmotion.antihealthindicator.data.cache.RidableEntity; -import com.deathmotion.antihealthindicator.data.cache.WolfEntity; -import com.deathmotion.antihealthindicator.managers.CacheManager; import com.deathmotion.antihealthindicator.managers.ConfigManager; -import com.github.retrooper.packetevents.event.PacketListener; import com.github.retrooper.packetevents.event.PacketSendEvent; -import com.github.retrooper.packetevents.event.UserDisconnectEvent; import com.github.retrooper.packetevents.protocol.entity.type.EntityType; import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes; import com.github.retrooper.packetevents.protocol.packettype.PacketType; @@ -36,36 +35,21 @@ import com.github.retrooper.packetevents.protocol.player.User; import com.github.retrooper.packetevents.wrapper.play.server.*; -import java.util.UUID; - /** * Listens for EntityState events and manages the caching of various entity state details. - * - * @param

The platform type. */ -public class EntityTracker

implements PacketListener { - private final CacheManager

cacheManager; - private final ConfigManager

configManager; - - /** - * Constructs a new EntityState with the specified {@link AHIPlatform}. - * - * @param platform The platform to use. - */ - public EntityTracker(AHIPlatform

platform) { - this.cacheManager = platform.getCacheManager(); - this.configManager = platform.getConfigManager(); - - platform.getLogManager().debug("Entity State listener has been set up."); +public class EntityTracker { + private final AHIPlayer player; + private final EntityCache entityCache; + private final ConfigManager configManager; + + + public EntityTracker(AHIPlayer player, EntityCache entityCache) { + this.player = player; + this.entityCache = entityCache; + this.configManager = AHIPlatform.getInstance().getConfigManager(); } - /** - * This function is called when an {@link PacketSendEvent} is triggered. - * Manages the state of various entities based on the event triggered. - * - * @param event The event that has been triggered. - */ - @Override public void onPacketSend(PacketSendEvent event) { final Settings settings = configManager.getSettings(); if (!settings.getEntityData().isEnabled()) return; @@ -73,33 +57,25 @@ public void onPacketSend(PacketSendEvent event) { final PacketTypeCommon type = event.getPacketType(); if (PacketType.Play.Server.SPAWN_LIVING_ENTITY == type) { - handleSpawnLivingEntity(new WrapperPlayServerSpawnLivingEntity(event), event.getUser(), settings); + handleSpawnLivingEntity(new WrapperPlayServerSpawnLivingEntity(event), settings); } else if (PacketType.Play.Server.SPAWN_ENTITY == type) { - handleSpawnEntity(new WrapperPlayServerSpawnEntity(event), event.getUser(), settings); + handleSpawnEntity(new WrapperPlayServerSpawnEntity(event), settings); } else if (PacketType.Play.Server.SPAWN_PLAYER == type) { - handleSpawnPlayer(new WrapperPlayServerSpawnPlayer(event), event.getUser()); + handleSpawnPlayer(new WrapperPlayServerSpawnPlayer(event)); } else if (PacketType.Play.Server.ENTITY_METADATA == type) { handleEntityMetadata(new WrapperPlayServerEntityMetadata(event), event.getUser(), settings); } else if (PacketType.Play.Server.DESTROY_ENTITIES == type) { - handleDestroyEntities(new WrapperPlayServerDestroyEntities(event), event.getUser()); + handleDestroyEntities(new WrapperPlayServerDestroyEntities(event)); } else if (PacketType.Play.Server.RESPAWN == type) { - handleRespawn(event.getUser()); + handleRespawn(); } else if (PacketType.Play.Server.JOIN_GAME == type) { - handleJoinGame(event.getUser()); + handleJoinGame(); } else if (PacketType.Play.Server.CONFIGURATION_START == type) { - handleConfigurationStart(event.getUser()); + handleConfigurationStart(); } } - @Override - public void onUserDisconnect(UserDisconnectEvent event) { - UUID userUUID = event.getUser().getUUID(); - if (userUUID == null) return; - - cacheManager.removeUserCache(userUUID); - } - - private void handleSpawnLivingEntity(WrapperPlayServerSpawnLivingEntity packet, User user, Settings settings) { + private void handleSpawnLivingEntity(WrapperPlayServerSpawnLivingEntity packet, Settings settings) { EntityType entityType = packet.getEntityType(); if (settings.getEntityData().isPlayersOnly()) { @@ -109,10 +85,10 @@ private void handleSpawnLivingEntity(WrapperPlayServerSpawnLivingEntity packet, int entityId = packet.getEntityId(); CachedEntity entityData = createLivingEntity(entityType); - cacheManager.addLivingEntity(user.getUUID(), entityId, entityData); + entityCache.addLivingEntity(entityId, entityData); } - private void handleSpawnEntity(WrapperPlayServerSpawnEntity packet, User user, Settings settings) { + private void handleSpawnEntity(WrapperPlayServerSpawnEntity packet, Settings settings) { EntityType entityType = packet.getEntityType(); if (EntityTypes.isTypeInstanceOf(entityType, EntityTypes.LIVINGENTITY)) { @@ -123,15 +99,15 @@ private void handleSpawnEntity(WrapperPlayServerSpawnEntity packet, User user, S int entityId = packet.getEntityId(); CachedEntity entityData = createLivingEntity(entityType); - cacheManager.addLivingEntity(user.getUUID(), entityId, entityData); + entityCache.addLivingEntity(entityId, entityData); } } - private void handleSpawnPlayer(WrapperPlayServerSpawnPlayer packet, User user) { + private void handleSpawnPlayer(WrapperPlayServerSpawnPlayer packet) { CachedEntity livingEntityData = new CachedEntity(); livingEntityData.setEntityType(EntityTypes.PLAYER); - cacheManager.addLivingEntity(user.getUUID(), packet.getEntityId(), livingEntityData); + entityCache.addLivingEntity(packet.getEntityId(), livingEntityData); } private void handleEntityMetadata(WrapperPlayServerEntityMetadata packet, User user, Settings settings) { @@ -139,28 +115,28 @@ private void handleEntityMetadata(WrapperPlayServerEntityMetadata packet, User u int entityId = packet.getEntityId(); - CachedEntity entityData = cacheManager.getCachedEntity(user.getUUID(), entityId).orElse(null); + CachedEntity entityData = entityCache.getCachedEntity(entityId).orElse(null); if (entityData == null) return; - packet.getEntityMetadata().forEach(metaData -> entityData.processMetaData(metaData, user)); + packet.getEntityMetadata().forEach(metaData -> entityData.processMetaData(metaData, player)); } - private void handleDestroyEntities(WrapperPlayServerDestroyEntities packet, User user) { + private void handleDestroyEntities(WrapperPlayServerDestroyEntities packet) { for (int entityId : packet.getEntityIds()) { - cacheManager.removeEntity(user.getUUID(), entityId); + entityCache.removeEntity(entityId); } } - private void handleRespawn(User user) { - cacheManager.resetUserCache(user.getUUID()); + private void handleRespawn() { + entityCache.resetUserCache(); } - private void handleJoinGame(User user) { - cacheManager.resetUserCache(user.getUUID()); + private void handleJoinGame() { + entityCache.resetUserCache(); } - private void handleConfigurationStart(User user) { - cacheManager.resetUserCache(user.getUUID()); + private void handleConfigurationStart() { + entityCache.resetUserCache(); } private CachedEntity createLivingEntity(EntityType entityType) { diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/cache/trackers/VehicleTracker.java b/common/src/main/java/com/deathmotion/antihealthindicator/cache/trackers/VehicleTracker.java new file mode 100644 index 0000000..8f0076c --- /dev/null +++ b/common/src/main/java/com/deathmotion/antihealthindicator/cache/trackers/VehicleTracker.java @@ -0,0 +1,122 @@ +/* + * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator + * Copyright (C) 2025 Bram and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.deathmotion.antihealthindicator.cache.trackers; + +import com.deathmotion.antihealthindicator.AHIPlatform; +import com.deathmotion.antihealthindicator.cache.EntityCache; +import com.deathmotion.antihealthindicator.cache.entities.CachedEntity; +import com.deathmotion.antihealthindicator.data.AHIPlayer; +import com.deathmotion.antihealthindicator.data.RidableEntities; +import com.deathmotion.antihealthindicator.data.Settings; +import com.deathmotion.antihealthindicator.managers.ConfigManager; +import com.github.retrooper.packetevents.event.PacketSendEvent; +import com.github.retrooper.packetevents.protocol.entity.data.EntityData; +import com.github.retrooper.packetevents.protocol.entity.data.EntityDataTypes; +import com.github.retrooper.packetevents.protocol.packettype.PacketType; +import com.github.retrooper.packetevents.protocol.packettype.PacketTypeCommon; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerAttachEntity; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityMetadata; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerSetPassengers; + +import java.util.Collections; +import java.util.List; + +/** + * Listens for VehicleState events and manages the caching of various entity state details. + */ +public class VehicleTracker { + private final AHIPlayer player; + private final EntityCache entityCache; + private final ConfigManager configManager; + + public VehicleTracker(AHIPlayer player, EntityCache entityCache) { + this.player = player; + this.entityCache = entityCache; + this.configManager = AHIPlatform.getInstance().getConfigManager(); + } + + public void onPacketSend(PacketSendEvent event) { + final Settings settings = configManager.getSettings(); + if (!settings.getEntityData().isEnabled()) return; + if (settings.getEntityData().isPlayersOnly()) return; + + final PacketTypeCommon type = event.getPacketType(); + + if (PacketType.Play.Server.SET_PASSENGERS == type) { + handlePassengers(new WrapperPlayServerSetPassengers(event)); + } else if (PacketType.Play.Server.ATTACH_ENTITY == type) { + handleAttachEntity(new WrapperPlayServerAttachEntity(event)); + } + } + + private void handlePassengers(WrapperPlayServerSetPassengers packet) { + int entityId = packet.getEntityId(); + if (entityId == player.user.getEntityId() || !isValidVehicle(entityId)) return; + + int[] passengers = packet.getPassengers(); + if (passengers.length > 0) { + updatePassengerState(entityId, passengers[0], true); + } else { + int passengerId = entityCache.getPassengerId(entityId); + updatePassengerState(entityId, passengerId, false); + } + } + + private void handleAttachEntity(WrapperPlayServerAttachEntity packet) { + int entityId = packet.getHoldingId(); + if (entityId == player.user.getEntityId() || !isValidVehicle(entityId)) return; + + int passengerId = packet.getAttachedId(); + if (entityId > 0) { + updatePassengerState(entityId, passengerId, true); + } else { + int reversedEntityId = entityCache.getEntityIdByPassengerId(passengerId); + updatePassengerState(reversedEntityId, passengerId, false); + } + } + + private void updatePassengerState(int vehicleId, int passengerId, boolean entering) { + entityCache.updateVehiclePassenger(vehicleId, entering ? passengerId : -1); + if (entering || player.user.getEntityId() == passengerId) { + float healthValue = entering ? entityCache.getVehicleHealth(vehicleId) : 0.5F; + sendVehicleHealthUpdate(vehicleId, healthValue); + } + } + + private boolean isValidVehicle(int entityId) { + return entityCache.getCachedEntity(entityId) + .map(CachedEntity::getEntityType) + .map(RidableEntities::isRideable) + .orElse(false); + } + + private void sendVehicleHealthUpdate(int vehicleId, float healthValue) { + AHIPlatform.getInstance().getScheduler().runAsyncTask((o) -> { + List metadata = Collections.singletonList( + new EntityData( + player.metadataIndex.HEALTH, + EntityDataTypes.FLOAT, + healthValue + ) + ); + + player.user.sendPacketSilently(new WrapperPlayServerEntityMetadata(vehicleId, metadata)); + }); + } +} \ No newline at end of file diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/data/AHIPlayer.java b/common/src/main/java/com/deathmotion/antihealthindicator/data/AHIPlayer.java new file mode 100644 index 0000000..1b6f694 --- /dev/null +++ b/common/src/main/java/com/deathmotion/antihealthindicator/data/AHIPlayer.java @@ -0,0 +1,46 @@ +/* + * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator + * Copyright (C) 2025 Bram and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.deathmotion.antihealthindicator.data; + +import com.deathmotion.antihealthindicator.cache.EntityCache; +import com.deathmotion.antihealthindicator.managers.SpoofManager; +import com.deathmotion.antihealthindicator.util.MetadataIndex; +import com.github.retrooper.packetevents.protocol.player.User; + +import java.util.UUID; + +public class AHIPlayer { + public final UUID uuid; + public final User user; + + public final MetadataIndex metadataIndex; + public final EntityCache entityCache; + + public final SpoofManager spoofManager; + + public AHIPlayer(User user) { + this.uuid = user.getUUID(); + this.user = user; + + this.metadataIndex = new MetadataIndex(user.getClientVersion()); + this.entityCache = new EntityCache(this); + + this.spoofManager = new SpoofManager(this); + } +} diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/data/Constants.java b/common/src/main/java/com/deathmotion/antihealthindicator/data/Constants.java index 39f0d9f..8b11ccf 100644 --- a/common/src/main/java/com/deathmotion/antihealthindicator/data/Constants.java +++ b/common/src/main/java/com/deathmotion/antihealthindicator/data/Constants.java @@ -21,6 +21,6 @@ public class Constants { public static final String GITHUB_API_URL = "https://api.github.com/repos/Bram1903/AntiHealthIndicator/releases/latest"; public static final String GITHUB_URL = "https://github.com/Bram1903/AntiHealthIndicator"; - public static final String SPIGOT_URL = "https://www.spigotmc.org/resources/antihealthindicator.114851/"; + public static final String MODRINTH_URL = "https://modrinth.com/plugin/antihealthindicator"; } diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/data/RidableEntities.java b/common/src/main/java/com/deathmotion/antihealthindicator/data/RidableEntities.java index 7d4993d..2bae56d 100644 --- a/common/src/main/java/com/deathmotion/antihealthindicator/data/RidableEntities.java +++ b/common/src/main/java/com/deathmotion/antihealthindicator/data/RidableEntities.java @@ -1,19 +1,19 @@ /* - * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator - * Copyright (C) 2025 Bram and contributors + * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator + * Copyright (C) 2025 Bram and contributors * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * 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 General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package com.deathmotion.antihealthindicator.data; diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/data/Settings.java b/common/src/main/java/com/deathmotion/antihealthindicator/data/Settings.java index 5986ab5..8aac5bb 100644 --- a/common/src/main/java/com/deathmotion/antihealthindicator/data/Settings.java +++ b/common/src/main/java/com/deathmotion/antihealthindicator/data/Settings.java @@ -27,7 +27,6 @@ public class Settings { private boolean Debug = false; private UpdateChecker UpdateChecker = new UpdateChecker(); - private boolean AllowBypass = false; private boolean WorldSeed = false; private boolean FoodSaturation = true; private boolean TeamScoreboard = true; diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/data/cache/CachedEntity.java b/common/src/main/java/com/deathmotion/antihealthindicator/data/cache/CachedEntity.java deleted file mode 100644 index 84d95fe..0000000 --- a/common/src/main/java/com/deathmotion/antihealthindicator/data/cache/CachedEntity.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator - * Copyright (C) 2025 Bram and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.deathmotion.antihealthindicator.data.cache; - -import com.github.retrooper.packetevents.protocol.entity.data.EntityData; -import com.github.retrooper.packetevents.protocol.entity.type.EntityType; -import com.github.retrooper.packetevents.protocol.player.User; -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -public class CachedEntity { - private EntityType entityType; - - public void processMetaData(EntityData metaData, User user) { - } -} \ No newline at end of file diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/data/cache/RidableEntity.java b/common/src/main/java/com/deathmotion/antihealthindicator/data/cache/RidableEntity.java deleted file mode 100644 index c3e5b03..0000000 --- a/common/src/main/java/com/deathmotion/antihealthindicator/data/cache/RidableEntity.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator - * Copyright (C) 2025 Bram and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.deathmotion.antihealthindicator.data.cache; - -import com.deathmotion.antihealthindicator.util.MetadataIndex; -import com.github.retrooper.packetevents.protocol.entity.data.EntityData; -import com.github.retrooper.packetevents.protocol.player.User; -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -public class RidableEntity extends CachedEntity { - private float health; - private int passengerId; - - @Override - public void processMetaData(EntityData metaData, User user) { - if (metaData.getIndex() == new MetadataIndex(user.getClientVersion()).HEALTH) { - setHealth((float) metaData.getValue()); - } - } -} \ No newline at end of file diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/data/cache/WolfEntity.java b/common/src/main/java/com/deathmotion/antihealthindicator/data/cache/WolfEntity.java deleted file mode 100644 index 1221b24..0000000 --- a/common/src/main/java/com/deathmotion/antihealthindicator/data/cache/WolfEntity.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator - * Copyright (C) 2025 Bram and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.deathmotion.antihealthindicator.data.cache; - -import com.deathmotion.antihealthindicator.util.MetadataIndex; -import com.github.retrooper.packetevents.protocol.entity.data.EntityData; -import com.github.retrooper.packetevents.protocol.player.User; -import lombok.Getter; -import lombok.Setter; - -import java.util.Optional; -import java.util.UUID; - -@Getter -@Setter -public class WolfEntity extends CachedEntity { - private boolean isTamed; - private UUID ownerUUID; - - public boolean isOwnerPresent() { - return ownerUUID != null; - } - - public UUID getOwnerUUID() { - if (!isOwnerPresent()) { - throw new IllegalStateException("Owner UUID not present"); - } - return ownerUUID; - } - - @Override - public void processMetaData(EntityData metaData, User user) { - int index = metaData.getIndex(); - MetadataIndex metadataIndex = new MetadataIndex(user.getClientVersion()); - - if (index == metadataIndex.TAMABLE_TAMED) { - setTamed(((Byte) metaData.getValue() & 0x04) != 0); - } else if (index == metadataIndex.TAMABLE_OWNER) { - Object value = metaData.getValue(); - - UUID ownerUUID = value instanceof String - ? Optional.of((String) value) - .filter(user.getUUID().toString()::equals) - .map(UUID::fromString) - .orElse(null) - : ((Optional) value) - .filter(user.getUUID()::equals) - .orElse(null); - - setOwnerUUID(ownerUUID); - } - } -} diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/managers/CacheManager.java b/common/src/main/java/com/deathmotion/antihealthindicator/managers/CacheManager.java deleted file mode 100644 index ec4c3db..0000000 --- a/common/src/main/java/com/deathmotion/antihealthindicator/managers/CacheManager.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator - * Copyright (C) 2025 Bram and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.deathmotion.antihealthindicator.managers; - -import com.deathmotion.antihealthindicator.AHIPlatform; -import com.deathmotion.antihealthindicator.data.cache.CachedEntity; -import com.deathmotion.antihealthindicator.data.cache.RidableEntity; -import lombok.Getter; -import lombok.NonNull; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; -import net.kyori.adventure.text.format.TextDecoration; - -import java.util.Map; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; - -/** - * Manages the cache services of the platform. - * - * @param

The platform type. - */ -@Getter -public class CacheManager

{ - private final ConcurrentHashMap> cache; - - private final AHIPlatform

platform; - private final ConfigManager

configManager; - private final LogManager

logManager; - - public CacheManager(AHIPlatform

platform) { - this.cache = new ConcurrentHashMap<>(); - - this.platform = platform; - this.logManager = platform.getLogManager(); - this.configManager = platform.getConfigManager(); - - LogCacheStats(); - - this.platform.getLogManager().debug("CacheManager initialized."); - } - - public Map getUserCache(@NonNull UUID uuid) { - return this.cache.computeIfAbsent(uuid, u -> new ConcurrentHashMap<>()); - } - - public void removeUserCache(@NonNull UUID uuid) { - this.cache.remove(uuid); - } - - public Optional getCachedEntity(@NonNull UUID uuid, int entityId) { - return Optional.ofNullable(getUserCache(uuid).get(entityId)); - } - - public Optional getVehicleData(@NonNull UUID uuid, int entityId) { - return getCachedEntity(uuid, entityId) - .filter(entityData -> entityData instanceof RidableEntity) - .map(entityData -> (RidableEntity) entityData); - } - - public void addLivingEntity(@NonNull UUID uuid, int entityId, @NonNull CachedEntity cachedEntity) { - getUserCache(uuid).put(entityId, cachedEntity); - } - - public void removeEntity(@NonNull UUID uuid, int entityId) { - getUserCache(uuid).remove(entityId); - } - - public void resetUserCache(@NonNull UUID uuid) { - getUserCache(uuid).clear(); - } - - public void updateVehiclePassenger(@NonNull UUID uuid, int entityId, int passengerId) { - getVehicleData(uuid, entityId).ifPresent(ridableEntityData -> ridableEntityData.setPassengerId(passengerId)); - } - - public float getVehicleHealth(@NonNull UUID uuid, int entityId) { - return getVehicleData(uuid, entityId).map(RidableEntity::getHealth).orElse(0.5f); - } - - public boolean isUserPassenger(@NonNull UUID uuid, int entityId, int userId) { - return getVehicleData(uuid, entityId).map(ridableEntityData -> ridableEntityData.getPassengerId() == userId).orElse(false); - } - - public int getPassengerId(@NonNull UUID uuid, int entityId) { - return getVehicleData(uuid, entityId).map(RidableEntity::getPassengerId).orElse(0); - } - - public int getEntityIdByPassengerId(@NonNull UUID uuid, int passengerId) { - return getUserCache(uuid).entrySet().stream() - .filter(entry -> entry.getValue() instanceof RidableEntity - && ((RidableEntity) entry.getValue()).getPassengerId() == passengerId) - .map(Map.Entry::getKey) - .findFirst() - .orElse(0); - } - - private void LogCacheStats() { - platform.getScheduler().runAsyncTaskAtFixedRate((o) -> { - if (!configManager.getSettings().isDebug()) return; - - ConcurrentHashMap> cacheMap = cache; - - int underlyingSize = cacheMap.values().stream().mapToInt(Map::size).sum(); - int avgCacheSizePerUser = cacheMap.isEmpty() ? 0 : Math.floorDiv(underlyingSize, cacheMap.size()); - - Component statsComponent = Component.text() - .append(Component.text("[DEBUG] Cache Stats", NamedTextColor.GREEN) - .decoration(TextDecoration.BOLD, true)) - .appendNewline() - .append(Component.text("\n\u25cf User Cache Size: ", NamedTextColor.GREEN) - .decoration(TextDecoration.BOLD, true)) - .append(Component.text(cache.size(), NamedTextColor.AQUA)) - .append(Component.text("\n\u25cf Average Cache Size Per User: ", NamedTextColor.GREEN) - .decoration(TextDecoration.BOLD, true)) - .append(Component.text(avgCacheSizePerUser, NamedTextColor.AQUA)) - .append(Component.text("\n\u25cf Underlying Cache Size: ", NamedTextColor.GREEN) - .decoration(TextDecoration.BOLD, true)) - .append(Component.text(underlyingSize, NamedTextColor.AQUA)) - .build(); - - platform.broadcastComponent(statsComponent, "AntiHealthIndicator.Debug"); - }, 10, 10, TimeUnit.SECONDS); - } -} \ No newline at end of file diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/managers/ConfigManager.java b/common/src/main/java/com/deathmotion/antihealthindicator/managers/ConfigManager.java index 20af134..345a0ac 100644 --- a/common/src/main/java/com/deathmotion/antihealthindicator/managers/ConfigManager.java +++ b/common/src/main/java/com/deathmotion/antihealthindicator/managers/ConfigManager.java @@ -92,7 +92,6 @@ private void setConfigOptions(Map yamlData, Settings settings) { settings.getUpdateChecker().setEnabled(getBoolean(yamlData, "update-checker.enabled", true)); settings.getUpdateChecker().setPrintToConsole(getBoolean(yamlData, "update-checker.print-to-console", true)); settings.getUpdateChecker().setNotifyInGame(getBoolean(yamlData, "update-checker.notify-in-game", true)); - settings.setAllowBypass(getBoolean(yamlData, "allow-bypass.enabled", false)); settings.setWorldSeed(getBoolean(yamlData, "spoof.world-seed.enabled", false)); settings.setFoodSaturation(getBoolean(yamlData, "spoof.food-saturation.enabled", true)); settings.setTeamScoreboard(getBoolean(yamlData, "spoof.team-scoreboard.enabled", true)); diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/managers/PacketManager.java b/common/src/main/java/com/deathmotion/antihealthindicator/managers/PacketManager.java deleted file mode 100644 index 1accdaa..0000000 --- a/common/src/main/java/com/deathmotion/antihealthindicator/managers/PacketManager.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator - * Copyright (C) 2025 Bram and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.deathmotion.antihealthindicator.managers; - -import com.deathmotion.antihealthindicator.AHIPlatform; -import com.deathmotion.antihealthindicator.packetlisteners.EntityTracker; -import com.deathmotion.antihealthindicator.packetlisteners.VehicleState; -import com.deathmotion.antihealthindicator.packetlisteners.spoofers.*; -import com.github.retrooper.packetevents.PacketEvents; -import com.github.retrooper.packetevents.event.PacketListenerPriority; - -/** - * Manager for handling packet listeners - * - * @param

The platform type - */ -public class PacketManager

{ - private final AHIPlatform

platform; - - /** - * Constructs a new PacketManager with the specified {@link AHIPlatform}. - * - * @param platform The platform to use. - */ - public PacketManager(AHIPlatform

platform) { - this.platform = platform; - - setupPacketListeners(); - platform.getLogManager().debug("Packet listeners have been set up."); - } - - /** - * Sets up packet listeners - */ - public void setupPacketListeners() { - PacketEvents.getAPI().getEventManager().registerListener(new EntityTracker<>(platform), PacketListenerPriority.LOW); - PacketEvents.getAPI().getEventManager().registerListener(new EntityMetadataListener<>(platform)); - PacketEvents.getAPI().getEventManager().registerListener(new VehicleState<>(platform)); - PacketEvents.getAPI().getEventManager().registerListener(new EntityEquipmentListener<>(platform)); - PacketEvents.getAPI().getEventManager().registerListener(new PlayerUpdateHealthListener<>(platform)); - PacketEvents.getAPI().getEventManager().registerListener(new WorldSeedListener<>(platform)); - PacketEvents.getAPI().getEventManager().registerListener(new ScoreboardListener<>(platform)); - } -} \ No newline at end of file diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/managers/PlayerDataManager.java b/common/src/main/java/com/deathmotion/antihealthindicator/managers/PlayerDataManager.java new file mode 100644 index 0000000..756dd3a --- /dev/null +++ b/common/src/main/java/com/deathmotion/antihealthindicator/managers/PlayerDataManager.java @@ -0,0 +1,59 @@ +/* + * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator + * Copyright (C) 2025 Bram and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.deathmotion.antihealthindicator.managers; + +import com.deathmotion.antihealthindicator.AHIPlatform; +import com.deathmotion.antihealthindicator.data.AHIPlayer; +import com.github.retrooper.packetevents.netty.channel.ChannelHelper; +import com.github.retrooper.packetevents.protocol.player.User; + +import javax.annotation.Nullable; +import java.util.concurrent.ConcurrentHashMap; + +public class PlayerDataManager

{ + + private final AHIPlatform

platform; + private final ConcurrentHashMap playerDataMap = new ConcurrentHashMap<>(); + + public PlayerDataManager(AHIPlatform

platform) { + this.platform = platform; + } + + public boolean shouldCheck(User user) { + if (!ChannelHelper.isOpen(user.getChannel())) return false; + if (user.getUUID() == null) return false; + + return !platform.hasPermission(user.getUUID(), "AntiHealthIndicator.Bypass"); + } + + @Nullable + public AHIPlayer getPlayer(final User user) { + return playerDataMap.get(user); + } + + public void addUser(final User user) { + if (shouldCheck(user)) { + playerDataMap.put(user, new AHIPlayer(user)); + } + } + + public void remove(final User player) { + playerDataMap.remove(player); + } +} diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/managers/SpoofManager.java b/common/src/main/java/com/deathmotion/antihealthindicator/managers/SpoofManager.java new file mode 100644 index 0000000..d4de60f --- /dev/null +++ b/common/src/main/java/com/deathmotion/antihealthindicator/managers/SpoofManager.java @@ -0,0 +1,47 @@ +/* + * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator + * Copyright (C) 2025 Bram and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.deathmotion.antihealthindicator.managers; + +import com.deathmotion.antihealthindicator.data.AHIPlayer; +import com.deathmotion.antihealthindicator.spoofers.impl.*; +import com.deathmotion.antihealthindicator.spoofers.type.PacketSpoofer; +import com.github.retrooper.packetevents.event.PacketSendEvent; +import com.google.common.collect.ClassToInstanceMap; +import com.google.common.collect.ImmutableClassToInstanceMap; + +public class SpoofManager { + + ClassToInstanceMap spoofers; + + public SpoofManager(AHIPlayer player) { + spoofers = new ImmutableClassToInstanceMap.Builder() + .put(MetadataSpoofer.class, new MetadataSpoofer(player)) + .put(EquipmentSpoofer.class, new EquipmentSpoofer(player)) + .put(ScoreboardSpoofer.class, new ScoreboardSpoofer(player)) + .put(FoodSaturationSpoofer.class, new FoodSaturationSpoofer(player)) + .put(WorldSeedSpoofer.class, new WorldSeedSpoofer(player)) + .build(); + } + + public void onPacketSend(final PacketSendEvent packet) { + for (PacketSpoofer packetSpoofer : spoofers.values()) { + packetSpoofer.onPacketSend(packet); + } + } +} diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/packetlisteners/UpdateNotifier.java b/common/src/main/java/com/deathmotion/antihealthindicator/packetlisteners/UpdateNotifier.java deleted file mode 100644 index a944566..0000000 --- a/common/src/main/java/com/deathmotion/antihealthindicator/packetlisteners/UpdateNotifier.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator - * Copyright (C) 2025 Bram and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.deathmotion.antihealthindicator.packetlisteners; - -import com.deathmotion.antihealthindicator.AHIPlatform; -import com.deathmotion.antihealthindicator.api.versioning.AHIVersion; -import com.deathmotion.antihealthindicator.data.Constants; -import com.deathmotion.antihealthindicator.data.Settings; -import com.github.retrooper.packetevents.event.PacketListenerAbstract; -import com.github.retrooper.packetevents.event.UserLoginEvent; -import com.github.retrooper.packetevents.protocol.player.User; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.event.ClickEvent; -import net.kyori.adventure.text.event.HoverEvent; -import net.kyori.adventure.text.format.NamedTextColor; -import net.kyori.adventure.text.format.TextDecoration; - -import java.util.concurrent.TimeUnit; - -/** - * Listens for PlayerJoin events and informs the user about the latest version of the application. - * - * @param

The platform type. - */ -public class UpdateNotifier

extends PacketListenerAbstract { - private final AHIPlatform

platform; - private final Component updateComponent; - - /** - * Constructs a new PlayerJoin with the specified {@link AHIPlatform} and latestVersion of the application. - * - * @param platform The platform to use. - * @param latestVersion The latest version of the application. - */ - public UpdateNotifier(AHIPlatform

platform, AHIVersion latestVersion) { - this.platform = platform; - - this.updateComponent = Component.text() - .append(Component.text("[AntiHealthIndicator] ", NamedTextColor.RED) - .decoration(TextDecoration.BOLD, true)) - .append(Component.text("Version " + latestVersion.toStringWithoutSnapshot() + " is ", NamedTextColor.GREEN)) - .append(Component.text("now available", NamedTextColor.GREEN) - .decorate(TextDecoration.UNDERLINED) - .hoverEvent(HoverEvent.showText(Component.text("Click to download", NamedTextColor.GREEN))) - .clickEvent(ClickEvent.openUrl(Constants.SPIGOT_URL))) - .append(Component.text("!", NamedTextColor.GREEN)) - .build(); - - platform.getLogManager().debug("Update detected. Player join listener has been set up."); - } - - /** - * This function is called when an {@link UserLoginEvent} is triggered. - * Sends a message to the player about the latest version of the application. - * - * @param event The event that has been triggered. - */ - @Override - public void onUserLogin(UserLoginEvent event) { - final Settings settings = platform.getConfigManager().getSettings(); - if (!settings.getUpdateChecker().isNotifyInGame()) return; - - User user = event.getUser(); - - platform.getScheduler().runAsyncTaskDelayed((o) -> { - if (platform.hasPermission(user.getUUID(), "AntiHealthIndicator.Notify")) { - user.sendMessage(updateComponent); - } - }, 2, TimeUnit.SECONDS); - } -} \ No newline at end of file diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/packetlisteners/VehicleState.java b/common/src/main/java/com/deathmotion/antihealthindicator/packetlisteners/VehicleState.java deleted file mode 100644 index 656a7ef..0000000 --- a/common/src/main/java/com/deathmotion/antihealthindicator/packetlisteners/VehicleState.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator - * Copyright (C) 2025 Bram and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.deathmotion.antihealthindicator.packetlisteners; - -import com.deathmotion.antihealthindicator.AHIPlatform; -import com.deathmotion.antihealthindicator.data.RidableEntities; -import com.deathmotion.antihealthindicator.data.Settings; -import com.deathmotion.antihealthindicator.data.cache.CachedEntity; -import com.deathmotion.antihealthindicator.managers.CacheManager; -import com.deathmotion.antihealthindicator.managers.ConfigManager; -import com.deathmotion.antihealthindicator.util.MetadataIndex; -import com.github.retrooper.packetevents.event.PacketListenerAbstract; -import com.github.retrooper.packetevents.event.PacketSendEvent; -import com.github.retrooper.packetevents.protocol.entity.data.EntityData; -import com.github.retrooper.packetevents.protocol.entity.data.EntityDataTypes; -import com.github.retrooper.packetevents.protocol.packettype.PacketType; -import com.github.retrooper.packetevents.protocol.packettype.PacketTypeCommon; -import com.github.retrooper.packetevents.protocol.player.User; -import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerAttachEntity; -import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityMetadata; -import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerSetPassengers; - -import java.util.Collections; -import java.util.List; -import java.util.UUID; - -/** - * Listens for VehicleState events and manages the caching of various entity state details. - * - * @param

The platform type. - */ -public class VehicleState

extends PacketListenerAbstract { - private final AHIPlatform

platform; - private final ConfigManager

configManager; - private final CacheManager

cacheManager; - - public VehicleState(AHIPlatform

platform) { - this.platform = platform; - this.configManager = platform.getConfigManager(); - this.cacheManager = platform.getCacheManager(); - - platform.getLogManager().debug("Vehicle State listener has been set up."); - } - - @Override - public void onPacketSend(PacketSendEvent event) { - final Settings settings = configManager.getSettings(); - if (!settings.getEntityData().isEnabled()) return; - if (settings.getEntityData().isPlayersOnly()) return; - - final PacketTypeCommon type = event.getPacketType(); - - if (PacketType.Play.Server.SET_PASSENGERS == type) { - handlePassengers(new WrapperPlayServerSetPassengers(event), event.getUser(), settings); - } else if (PacketType.Play.Server.ATTACH_ENTITY == type) { - handleAttachEntity(new WrapperPlayServerAttachEntity(event), event.getUser(), settings); - } - } - - private void handlePassengers(WrapperPlayServerSetPassengers packet, User user, Settings settings) { - int entityId = packet.getEntityId(); - if (entityId == user.getEntityId() || !isValidVehicle(user.getUUID(), entityId)) return; - - int[] passengers = packet.getPassengers(); - if (passengers.length > 0) { - updatePassengerState(user, entityId, passengers[0], true, settings); - } else { - int passengerId = cacheManager.getPassengerId(user.getUUID(), entityId); - updatePassengerState(user, entityId, passengerId, false, settings); - } - } - - private void handleAttachEntity(WrapperPlayServerAttachEntity packet, User user, Settings settings) { - int entityId = packet.getHoldingId(); - if (entityId == user.getEntityId() || !isValidVehicle(user.getUUID(), entityId)) return; - - int passengerId = packet.getAttachedId(); - if (entityId > 0) { - updatePassengerState(user, entityId, passengerId, true, settings); - } else { - int reversedEntityId = cacheManager.getEntityIdByPassengerId(user.getUUID(), passengerId); - updatePassengerState(user, reversedEntityId, passengerId, false, settings); - } - } - - private void updatePassengerState(User user, int vehicleId, int passengerId, boolean entering, Settings settings) { - cacheManager.updateVehiclePassenger(user.getUUID(), vehicleId, entering ? passengerId : -1); - if (entering || user.getEntityId() == passengerId) { - float healthValue = entering ? cacheManager.getVehicleHealth(user.getUUID(), vehicleId) : 0.5F; - sendVehicleHealthUpdate(user, vehicleId, healthValue, entering, settings); - } - } - - private boolean isValidVehicle(UUID userUUID, int entityId) { - return cacheManager.getCachedEntity(userUUID, entityId) - .map(CachedEntity::getEntityType) - .map(RidableEntities::isRideable) - .orElse(false); - } - - private void sendVehicleHealthUpdate(User user, int vehicleId, float healthValue, boolean entering, Settings settings) { - platform.getScheduler().runAsyncTask((o) -> { - if (!entering && settings.isAllowBypass() && platform.hasPermission(user.getUUID(), "AntiHealthIndicator.Bypass")) { - return; - } - - List metadata = Collections.singletonList( - new EntityData( - new MetadataIndex(user.getClientVersion()).HEALTH, - EntityDataTypes.FLOAT, - healthValue - ) - ); - - user.sendPacketSilently(new WrapperPlayServerEntityMetadata(vehicleId, metadata)); - }); - } -} \ No newline at end of file diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/packetlisteners/spoofers/EntityMetadataListener.java b/common/src/main/java/com/deathmotion/antihealthindicator/packetlisteners/spoofers/EntityMetadataListener.java deleted file mode 100644 index be7bae1..0000000 --- a/common/src/main/java/com/deathmotion/antihealthindicator/packetlisteners/spoofers/EntityMetadataListener.java +++ /dev/null @@ -1,216 +0,0 @@ -/* - * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator - * Copyright (C) 2025 Bram and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.deathmotion.antihealthindicator.packetlisteners.spoofers; - -import com.deathmotion.antihealthindicator.AHIPlatform; -import com.deathmotion.antihealthindicator.data.Settings; -import com.deathmotion.antihealthindicator.data.cache.CachedEntity; -import com.deathmotion.antihealthindicator.data.cache.WolfEntity; -import com.deathmotion.antihealthindicator.managers.CacheManager; -import com.deathmotion.antihealthindicator.managers.ConfigManager; -import com.deathmotion.antihealthindicator.util.MetadataIndex; -import com.github.retrooper.packetevents.PacketEvents; -import com.github.retrooper.packetevents.event.PacketListenerAbstract; -import com.github.retrooper.packetevents.event.PacketSendEvent; -import com.github.retrooper.packetevents.manager.server.ServerVersion; -import com.github.retrooper.packetevents.protocol.entity.data.EntityData; -import com.github.retrooper.packetevents.protocol.entity.type.EntityType; -import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes; -import com.github.retrooper.packetevents.protocol.packettype.PacketType; -import com.github.retrooper.packetevents.protocol.player.User; -import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityMetadata; - -/** - * Listens for EntityMetadata packets and modifies certain entity attributes - * based on configuration settings. - * - * @param

The platform type. - */ -public class EntityMetadataListener

extends PacketListenerAbstract { - private final AHIPlatform

platform; - private final ConfigManager

configManager; - private final CacheManager

cacheManager; - private final boolean healthTexturesSupported; - - public EntityMetadataListener(AHIPlatform

platform) { - this.platform = platform; - this.configManager = platform.getConfigManager(); - this.cacheManager = platform.getCacheManager(); - - // Health textures are supported for server versions 1.15 and above. - this.healthTexturesSupported = PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_15); - - platform.getLogManager().debug("Entity Metadata listener initialized."); - } - - @Override - public void onPacketSend(PacketSendEvent event) { - if (!event.getPacketType().equals(PacketType.Play.Server.ENTITY_METADATA)) return; - - Settings settings = configManager.getSettings(); - if (!settings.getEntityData().isEnabled()) return; - - WrapperPlayServerEntityMetadata packet = new WrapperPlayServerEntityMetadata(event); - int entityId = packet.getEntityId(); - User user = event.getUser(); - - // Do not process if the packet refers to the user’s own entity or if the user has bypass permissions. - if (entityId == user.getEntityId() || shouldBypass(user, settings)) return; - - CachedEntity cachedEntity = cacheManager.getCachedEntity(user.getUUID(), entityId).orElse(null); - if (cachedEntity == null) return; - - EntityType entityType = cachedEntity.getEntityType(); - if (shouldIgnoreEntity(entityType, user, entityId, cachedEntity, settings)) return; - - MetadataIndex metadataIndex = new MetadataIndex(user.getClientVersion()); - packet.getEntityMetadata().forEach(entityData -> handleEntityMetadata(entityType, entityData, metadataIndex, settings)); - event.markForReEncode(true); - } - - private boolean shouldBypass(User user, Settings settings) { - return settings.isAllowBypass() && platform.hasPermission(user.getUUID(), "AntiHealthIndicator.Bypass"); - } - - private boolean shouldIgnoreEntity(EntityType entityType, User user, int entityId, CachedEntity cachedEntity, Settings settings) { - // Ignore entities with a boss bar (that shows the health already anyway) - if (entityType == EntityTypes.WITHER || entityType == EntityTypes.ENDER_DRAGON) { - return true; - } - - // If only players should be processed, skip non-player entities. - if (settings.getEntityData().isPlayersOnly() && entityType != EntityTypes.PLAYER) { - return true; - } - - // Optionally ignore vehicles. - if (!settings.getEntityData().isPlayersOnly() && settings.getEntityData().isIgnoreVehicles() && cacheManager.isUserPassenger(user.getUUID(), entityId, user.getEntityId())) { - return true; - } - - // Special handling for wolves. - return entityType == EntityTypes.WOLF && settings.getEntityData().getWolves().isEnabled() && shouldIgnoreWolf(user, cachedEntity, settings); - } - - private boolean shouldIgnoreWolf(User user, CachedEntity cachedEntity, Settings settings) { - WolfEntity wolfEntity = (WolfEntity) cachedEntity; - boolean ignoreBasedOnSettings = !settings.getEntityData().getWolves().isTamed() && !settings.getEntityData().getWolves().isOwner(); - boolean isTamed = settings.getEntityData().getWolves().isTamed() && wolfEntity.isTamed(); - boolean isOwnedByUser = settings.getEntityData().getWolves().isOwner() && wolfEntity.isOwnerPresent() && wolfEntity.getOwnerUUID().equals(user.getUUID()); - return ignoreBasedOnSettings || isTamed || isOwnedByUser; - } - - /** - * Modifies the metadata for the given entity based on its type and settings. - */ - private void handleEntityMetadata(EntityType entityType, EntityData entityData, MetadataIndex metadataIndex, Settings settings) { - if (entityType == EntityTypes.IRON_GOLEM && settings.getEntityData().getIronGolems().isEnabled()) { - if (!settings.getEntityData().getIronGolems().isGradual() || !healthTexturesSupported) { - applyDefaultSpoofing(entityData, metadataIndex, settings); - } else { - spoofIronGolemMetadata(entityData, metadataIndex, settings); - } - } else { - applyDefaultSpoofing(entityData, metadataIndex, settings); - if (entityType == EntityTypes.PLAYER) { - spoofPlayerMetadata(entityData, metadataIndex, settings); - } - } - } - - /** - * Applies default spoofing logic for common entity metadata. - */ - private void applyDefaultSpoofing(EntityData entityData, MetadataIndex metadataIndex, Settings settings) { - updateAirTicks(entityData, metadataIndex, settings); - if (entityData.getIndex() == metadataIndex.HEALTH && settings.getEntityData().isHealth()) { - float health = (Float) entityData.getValue(); - if (health > 0) { - entityData.setValue(0.5f); - } - } - } - - /** - * Modifies the metadata for iron golems gradually. - */ - private void spoofIronGolemMetadata(EntityData entityData, MetadataIndex metadataIndex, Settings settings) { - updateAirTicks(entityData, metadataIndex, settings); - if (entityData.getIndex() == metadataIndex.HEALTH && settings.getEntityData().isHealth()) { - float health = (Float) entityData.getValue(); - if (health > 74f) { - entityData.setValue(100f); - } else if (health > 49f) { - entityData.setValue(74f); - } else if (health > 24f) { - entityData.setValue(49f); - } else { - entityData.setValue(24f); - } - } - } - - /** - * Modifies the metadata for player entities. - */ - private void spoofPlayerMetadata(EntityData entityData, MetadataIndex metadataIndex, Settings settings) { - if (entityData.getIndex() == metadataIndex.ABSORPTION && settings.getEntityData().isAbsorption()) { - setDynamicValue(entityData, 0); - } - if (entityData.getIndex() == metadataIndex.XP && settings.getEntityData().isXp()) { - setDynamicValue(entityData, 0); - } - } - - /** - * Updates the air ticks metadata if enabled. - */ - private void updateAirTicks(EntityData entityData, MetadataIndex metadataIndex, Settings settings) { - if (entityData.getIndex() == metadataIndex.AIR_TICKS && settings.getEntityData().isAirTicks()) { - setDynamicValue(entityData, 1); - } - } - - /** - * Sets a new value for the entity data while preserving its original numeric type. - *

- * This method is necessary because the metadata value is stored as an {@code Object} - * and can be of different numeric types (e.g., Integer, Short, Byte, Long, Float, or Double). - * - * @param entityData The metadata object to modify. - * @param spoofValue The new value to set. - */ - private void setDynamicValue(EntityData entityData, int spoofValue) { - Object value = entityData.getValue(); - - if (value instanceof Integer) { - entityData.setValue(spoofValue); - } else if (value instanceof Short) { - entityData.setValue((short) spoofValue); - } else if (value instanceof Byte) { - entityData.setValue((byte) spoofValue); - } else if (value instanceof Long) { - entityData.setValue((long) spoofValue); - } else if (value instanceof Float) { - entityData.setValue((float) spoofValue); - } else if (value instanceof Double) { - entityData.setValue((double) spoofValue); - } - } -} \ No newline at end of file diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/packetlisteners/spoofers/PlayerUpdateHealthListener.java b/common/src/main/java/com/deathmotion/antihealthindicator/packetlisteners/spoofers/PlayerUpdateHealthListener.java deleted file mode 100644 index 9115bd4..0000000 --- a/common/src/main/java/com/deathmotion/antihealthindicator/packetlisteners/spoofers/PlayerUpdateHealthListener.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator - * Copyright (C) 2025 Bram and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.deathmotion.antihealthindicator.packetlisteners.spoofers; - -import com.deathmotion.antihealthindicator.AHIPlatform; -import com.deathmotion.antihealthindicator.data.Settings; -import com.deathmotion.antihealthindicator.managers.ConfigManager; -import com.github.retrooper.packetevents.event.PacketListenerAbstract; -import com.github.retrooper.packetevents.event.PacketSendEvent; -import com.github.retrooper.packetevents.protocol.packettype.PacketType; -import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerUpdateHealth; - -/** - * Listens for PlayerUpdateHealth events to modify the health display. - * - * @param

The platform type. - */ -public class PlayerUpdateHealthListener

extends PacketListenerAbstract { - private final AHIPlatform

platform; - private final ConfigManager

configManager; - - /** - * Constructs a new PlayerUpdateHealthListener with the specified {@link AHIPlatform}. - * - * @param platform The platform to use. - */ - public PlayerUpdateHealthListener(AHIPlatform

platform) { - this.platform = platform; - this.configManager = platform.getConfigManager(); - - platform.getLogManager().debug("Player Update Health listener has been set up."); - } - - /** - * This function is called when an {@link PacketSendEvent} is triggered. - * Overwrites the {@link WrapperPlayServerUpdateHealth} for players to control how they are displayed. - * - * @param event The event that has been triggered. - */ - @Override - public void onPacketSend(PacketSendEvent event) { - if (event.getPacketType() != PacketType.Play.Server.UPDATE_HEALTH) return; - - final Settings settings = configManager.getSettings(); - if (!settings.isFoodSaturation()) return; - - if (settings.isAllowBypass()) { - if (platform.hasPermission(event.getUser().getUUID(), "AntiHealthIndicator.Bypass")) return; - } - - WrapperPlayServerUpdateHealth packet = new WrapperPlayServerUpdateHealth(event); - - if (packet.getFoodSaturation() > 0) { - packet.setFoodSaturation(Float.NaN); - event.markForReEncode(true); - } - } -} diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/packetlisteners/spoofers/WorldSeedListener.java b/common/src/main/java/com/deathmotion/antihealthindicator/packetlisteners/spoofers/WorldSeedListener.java deleted file mode 100644 index aa82e91..0000000 --- a/common/src/main/java/com/deathmotion/antihealthindicator/packetlisteners/spoofers/WorldSeedListener.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator - * Copyright (C) 2025 Bram and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.deathmotion.antihealthindicator.packetlisteners.spoofers; - -import com.deathmotion.antihealthindicator.AHIPlatform; -import com.deathmotion.antihealthindicator.data.Settings; -import com.deathmotion.antihealthindicator.managers.ConfigManager; -import com.github.retrooper.packetevents.event.PacketListenerAbstract; -import com.github.retrooper.packetevents.event.PacketSendEvent; -import com.github.retrooper.packetevents.protocol.packettype.PacketType; -import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerJoinGame; -import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerRespawn; - -/** - * Listens for WorldSeed events to modify the seed value. - * - * @param

The platform type. - */ -public class WorldSeedListener

extends PacketListenerAbstract { - - private final AHIPlatform

platform; - private final ConfigManager

settings; - - /** - * Constructs a new WorldSeedListener with the specified {@link AHIPlatform}. - * - * @param platform The platform to use. - */ - public WorldSeedListener(AHIPlatform

platform) { - this.platform = platform; - this.settings = platform.getConfigManager(); - - platform.getLogManager().debug("World Seed listener has been set up."); - } - - /** - * This function is called when an {@link PacketSendEvent} is triggered. - * Overwrites the {@link WrapperPlayServerJoinGame} and {@link WrapperPlayServerRespawn} packets - * to control seed value. - * - * @param event The event that has been triggered. - */ - @Override - public void onPacketSend(PacketSendEvent event) { - final Settings settings = this.settings.getSettings(); - if (!settings.isWorldSeed()) return; - - if (event.getPacketType().equals(PacketType.Play.Server.JOIN_GAME)) { - if (settings.isAllowBypass()) { - if (platform.hasPermission(event.getUser().getUUID(), "AntiHealthIndicator.Bypass")) return; - } - - WrapperPlayServerJoinGame wrapper = new WrapperPlayServerJoinGame(event); - wrapper.setHashedSeed(0L); - event.markForReEncode(true); - } - if (event.getPacketType().equals(PacketType.Play.Server.RESPAWN)) { - if (settings.isAllowBypass()) { - if (platform.hasPermission(event.getUser().getUUID(), "AntiHealthIndicator.Bypass")) return; - } - - WrapperPlayServerRespawn wrapper = new WrapperPlayServerRespawn(event); - wrapper.setHashedSeed(0L); - event.markForReEncode(true); - } - } -} \ No newline at end of file diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/packets/PacketPlayerJoinQuit.java b/common/src/main/java/com/deathmotion/antihealthindicator/packets/PacketPlayerJoinQuit.java new file mode 100644 index 0000000..341808b --- /dev/null +++ b/common/src/main/java/com/deathmotion/antihealthindicator/packets/PacketPlayerJoinQuit.java @@ -0,0 +1,62 @@ +/* + * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator + * Copyright (C) 2025 Bram and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.deathmotion.antihealthindicator.packets; + +import com.deathmotion.antihealthindicator.AHIPlatform; +import com.github.retrooper.packetevents.event.PacketListenerAbstract; +import com.github.retrooper.packetevents.event.PacketSendEvent; +import com.github.retrooper.packetevents.event.UserDisconnectEvent; +import com.github.retrooper.packetevents.event.UserLoginEvent; +import com.github.retrooper.packetevents.protocol.packettype.PacketType; +import com.github.retrooper.packetevents.protocol.player.User; + +import java.util.concurrent.TimeUnit; + +public class PacketPlayerJoinQuit

extends PacketListenerAbstract { + + private final AHIPlatform

platform; + + public PacketPlayerJoinQuit(AHIPlatform

platform) { + this.platform = platform; + } + + @Override + public void onPacketSend(PacketSendEvent event) { + if (event.getPacketType() == PacketType.Login.Server.LOGIN_SUCCESS) { + // Do this after send to avoid sending packets before the PLAY state + event.getTasksAfterSend().add(() -> platform.getPlayerDataManager().addUser(event.getUser())); + } + } + + @Override + public void onUserLogin(UserLoginEvent event) { + if (platform.getConfigManager().getSettings().getUpdateChecker().isNotifyInGame() && platform.getUpdateChecker().isUpdateAvailable()) { + User user = event.getUser(); + + if (platform.hasPermission(user.getUUID(), "AntiHealthIndicator.Update")) { + platform.getScheduler().runAsyncTaskDelayed((o) -> user.sendMessage(platform.getUpdateChecker().getUpdateComponent()), 2, TimeUnit.SECONDS); + } + } + } + + @Override + public void onUserDisconnect(UserDisconnectEvent event) { + platform.getPlayerDataManager().remove(event.getUser()); + } +} diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/packets/SpoofManagerPacketListener.java b/common/src/main/java/com/deathmotion/antihealthindicator/packets/SpoofManagerPacketListener.java new file mode 100644 index 0000000..61e6fa1 --- /dev/null +++ b/common/src/main/java/com/deathmotion/antihealthindicator/packets/SpoofManagerPacketListener.java @@ -0,0 +1,46 @@ +/* + * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator + * Copyright (C) 2025 Bram and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.deathmotion.antihealthindicator.packets; + +import com.deathmotion.antihealthindicator.AHIPlatform; +import com.deathmotion.antihealthindicator.data.AHIPlayer; +import com.github.retrooper.packetevents.event.PacketListenerAbstract; +import com.github.retrooper.packetevents.event.PacketListenerPriority; +import com.github.retrooper.packetevents.event.PacketSendEvent; +import com.github.retrooper.packetevents.protocol.ConnectionState; + +public class SpoofManagerPacketListener

extends PacketListenerAbstract { + + private final AHIPlatform

platform; + + public SpoofManagerPacketListener(AHIPlatform

platform) { + super(PacketListenerPriority.LOW); + this.platform = platform; + } + + @Override + public void onPacketSend(final PacketSendEvent event) { + if (event.getConnectionState() != ConnectionState.PLAY) return; + AHIPlayer player = platform.getPlayerDataManager().getPlayer(event.getUser()); + if (player == null) return; + + player.entityCache.onPacketSend(event); + player.spoofManager.onPacketSend(event); + } +} diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/spoofers/Spoofer.java b/common/src/main/java/com/deathmotion/antihealthindicator/spoofers/Spoofer.java new file mode 100644 index 0000000..e29efa4 --- /dev/null +++ b/common/src/main/java/com/deathmotion/antihealthindicator/spoofers/Spoofer.java @@ -0,0 +1,34 @@ +/* + * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator + * Copyright (C) 2025 Bram and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.deathmotion.antihealthindicator.spoofers; + +import com.deathmotion.antihealthindicator.AHIPlatform; +import com.deathmotion.antihealthindicator.data.AHIPlayer; +import com.deathmotion.antihealthindicator.managers.ConfigManager; + +public class Spoofer { + + protected final AHIPlayer player; + protected final ConfigManager configManager; + + public Spoofer(AHIPlayer player) { + this.player = player; + this.configManager = AHIPlatform.getInstance().getConfigManager(); + } +} diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/packetlisteners/spoofers/EntityEquipmentListener.java b/common/src/main/java/com/deathmotion/antihealthindicator/spoofers/impl/EquipmentSpoofer.java similarity index 70% rename from common/src/main/java/com/deathmotion/antihealthindicator/packetlisteners/spoofers/EntityEquipmentListener.java rename to common/src/main/java/com/deathmotion/antihealthindicator/spoofers/impl/EquipmentSpoofer.java index 9473316..86738e1 100644 --- a/common/src/main/java/com/deathmotion/antihealthindicator/packetlisteners/spoofers/EntityEquipmentListener.java +++ b/common/src/main/java/com/deathmotion/antihealthindicator/spoofers/impl/EquipmentSpoofer.java @@ -1,28 +1,28 @@ /* - * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator - * Copyright (C) 2025 Bram and contributors + * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator + * Copyright (C) 2025 Bram and contributors * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * 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 General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ -package com.deathmotion.antihealthindicator.packetlisteners.spoofers; +package com.deathmotion.antihealthindicator.spoofers.impl; -import com.deathmotion.antihealthindicator.AHIPlatform; +import com.deathmotion.antihealthindicator.data.AHIPlayer; import com.deathmotion.antihealthindicator.data.Settings; -import com.deathmotion.antihealthindicator.managers.ConfigManager; +import com.deathmotion.antihealthindicator.spoofers.Spoofer; +import com.deathmotion.antihealthindicator.spoofers.type.PacketSpoofer; import com.github.retrooper.packetevents.PacketEvents; -import com.github.retrooper.packetevents.event.PacketListenerAbstract; import com.github.retrooper.packetevents.event.PacketSendEvent; import com.github.retrooper.packetevents.manager.server.ServerVersion; import com.github.retrooper.packetevents.protocol.item.ItemStack; @@ -38,14 +38,7 @@ import java.util.Collections; import java.util.List; -/** - * Listens for EntityEquipment events to apply modifications. - * - * @param

The platform type. - */ -public class EntityEquipmentListener

extends PacketListenerAbstract { - private final AHIPlatform

platform; - private final ConfigManager

configManager; +public class EquipmentSpoofer extends Spoofer implements PacketSpoofer { private final boolean useDamageableInterface; @@ -57,25 +50,12 @@ public class EntityEquipmentListener

extends PacketListenerAbstract { .level(3) .build()); - /** - * Constructs a new EntityEquipmentListener with the specified platform. - * - * @param platform The platform to use. - */ - public EntityEquipmentListener(AHIPlatform

platform) { - this.platform = platform; - this.configManager = platform.getConfigManager(); + public EquipmentSpoofer(AHIPlayer player) { + super(player); this.useDamageableInterface = PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_13); - - platform.getLogManager().debug("Entity Equipment listener initialized."); } - /** - * Called when a packet is sent to the player - * - * @param event the packet sends event - */ @Override public void onPacketSend(PacketSendEvent event) { if (event.getPacketType() != PacketType.Play.Server.ENTITY_EQUIPMENT) return; @@ -84,11 +64,6 @@ public void onPacketSend(PacketSendEvent event) { if (!settings.getItems().isEnabled()) return; WrapperPlayServerEntityEquipment packet = new WrapperPlayServerEntityEquipment(event); - - if (settings.isAllowBypass()) { - if (platform.hasPermission(event.getUser().getUUID(), "AntiHealthIndicator.Bypass")) return; - } - List equipmentList = packet.getEquipment(); if (equipmentList.isEmpty()) { return; diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/spoofers/impl/FoodSaturationSpoofer.java b/common/src/main/java/com/deathmotion/antihealthindicator/spoofers/impl/FoodSaturationSpoofer.java new file mode 100644 index 0000000..52c50c9 --- /dev/null +++ b/common/src/main/java/com/deathmotion/antihealthindicator/spoofers/impl/FoodSaturationSpoofer.java @@ -0,0 +1,49 @@ +/* + * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator + * Copyright (C) 2025 Bram and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.deathmotion.antihealthindicator.spoofers.impl; + +import com.deathmotion.antihealthindicator.data.AHIPlayer; +import com.deathmotion.antihealthindicator.data.Settings; +import com.deathmotion.antihealthindicator.spoofers.Spoofer; +import com.deathmotion.antihealthindicator.spoofers.type.PacketSpoofer; +import com.github.retrooper.packetevents.event.PacketSendEvent; +import com.github.retrooper.packetevents.protocol.packettype.PacketType; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerUpdateHealth; + +public class FoodSaturationSpoofer extends Spoofer implements PacketSpoofer { + + public FoodSaturationSpoofer(AHIPlayer player) { + super(player); + } + + @Override + public void onPacketSend(PacketSendEvent event) { + if (event.getPacketType() != PacketType.Play.Server.UPDATE_HEALTH) return; + + final Settings settings = configManager.getSettings(); + if (!settings.isFoodSaturation()) return; + + WrapperPlayServerUpdateHealth packet = new WrapperPlayServerUpdateHealth(event); + + if (packet.getFoodSaturation() > 0) { + packet.setFoodSaturation(Float.NaN); + event.markForReEncode(true); + } + } +} diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/spoofers/impl/MetadataSpoofer.java b/common/src/main/java/com/deathmotion/antihealthindicator/spoofers/impl/MetadataSpoofer.java new file mode 100644 index 0000000..6a2280c --- /dev/null +++ b/common/src/main/java/com/deathmotion/antihealthindicator/spoofers/impl/MetadataSpoofer.java @@ -0,0 +1,221 @@ +/* + * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator + * Copyright (C) 2025 Bram and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.deathmotion.antihealthindicator.spoofers.impl; + +import com.deathmotion.antihealthindicator.cache.EntityCache; +import com.deathmotion.antihealthindicator.cache.entities.CachedEntity; +import com.deathmotion.antihealthindicator.cache.entities.WolfEntity; +import com.deathmotion.antihealthindicator.data.AHIPlayer; +import com.deathmotion.antihealthindicator.data.Settings; +import com.deathmotion.antihealthindicator.spoofers.Spoofer; +import com.deathmotion.antihealthindicator.spoofers.type.PacketSpoofer; +import com.github.retrooper.packetevents.PacketEvents; +import com.github.retrooper.packetevents.event.PacketSendEvent; +import com.github.retrooper.packetevents.manager.server.ServerVersion; +import com.github.retrooper.packetevents.protocol.entity.data.EntityData; +import com.github.retrooper.packetevents.protocol.entity.type.EntityType; +import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes; +import com.github.retrooper.packetevents.protocol.packettype.PacketType; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityMetadata; + +public class MetadataSpoofer extends Spoofer implements PacketSpoofer { + + // Constants for Iron Golem health thresholds + private static final float IRON_GOLEM_HEALTH_MAX = 100f; + private static final float IRON_GOLEM_THRESHOLD_1 = 74f; + private static final float IRON_GOLEM_THRESHOLD_2 = 49f; + private static final float IRON_GOLEM_THRESHOLD_3 = 24f; + + private final EntityCache entityCache; + private final boolean healthTexturesSupported; + + public MetadataSpoofer(AHIPlayer player) { + super(player); + this.entityCache = player.entityCache; + this.healthTexturesSupported = PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_15); + } + + @Override + public void onPacketSend(PacketSendEvent event) { + if (!event.getPacketType().equals(PacketType.Play.Server.ENTITY_METADATA)) return; + + Settings settings = configManager.getSettings(); + if (!settings.getEntityData().isEnabled()) return; + + WrapperPlayServerEntityMetadata packet = new WrapperPlayServerEntityMetadata(event); + int entityId = packet.getEntityId(); + + // Skip processing if the packet refers to the user's own entity. + if (entityId == player.user.getEntityId()) return; + + CachedEntity cachedEntity = entityCache.getCachedEntity(entityId).orElse(null); + if (cachedEntity == null) return; + + EntityType entityType = cachedEntity.getEntityType(); + if (shouldIgnoreEntity(entityType, entityId, cachedEntity, settings)) return; + + // Process each metadata entry for spoofing. + packet.getEntityMetadata().forEach(entityData -> handleEntityMetadata(entityType, entityData, settings)); + + event.markForReEncode(true); + } + + private boolean shouldIgnoreEntity(EntityType entityType, int entityId, CachedEntity cachedEntity, Settings settings) { + // Ignore entities with built-in health displays (e.g., bosses). + if (entityType == EntityTypes.WITHER || entityType == EntityTypes.ENDER_DRAGON) { + return true; + } + + // If only players should be processed, skip non-player entities. + if (settings.getEntityData().isPlayersOnly() && entityType != EntityTypes.PLAYER) { + return true; + } + + // Optionally ignore vehicles. + if (!settings.getEntityData().isPlayersOnly() && settings.getEntityData().isIgnoreVehicles() && entityCache.isUserPassenger(entityId)) { + return true; + } + + // Special handling for wolves. + if (entityType == EntityTypes.WOLF && settings.getEntityData().getWolves().isEnabled()) { + return shouldIgnoreWolf(cachedEntity, settings); + } + + return false; + } + + /** + * Determines whether a wolf entity should be ignored based on its tamed/owner state and the settings. + */ + private boolean shouldIgnoreWolf(CachedEntity cachedEntity, Settings settings) { + WolfEntity wolfEntity = (WolfEntity) cachedEntity; + Settings.EntityData.Wolves wolfSettings = settings.getEntityData().getWolves(); + + // If neither tamed nor owner conditions are enabled, ignore the wolf. + if (!wolfSettings.isTamed() && !wolfSettings.isOwner()) { + return true; + } + // Ignore if the wolf is tamed and tamed wolves should be ignored. + if (wolfSettings.isTamed() && wolfEntity.isTamed()) { + return true; + } + + // Ignore if the user owns the wolf and owner wolves should be ignored. + return wolfSettings.isOwner() && wolfEntity.isOwnerPresent() && wolfEntity.getOwnerUUID().equals(player.uuid); + } + + /** + * Modifies the metadata for the given entity based on its type and the configured settings. + */ + private void handleEntityMetadata(EntityType entityType, EntityData entityData, Settings settings) { + if (entityType == EntityTypes.IRON_GOLEM && settings.getEntityData().getIronGolems().isEnabled()) { + if (!settings.getEntityData().getIronGolems().isGradual() || !healthTexturesSupported) { + applyDefaultSpoofing(entityData, settings); + } else { + spoofIronGolemMetadata(entityData, settings); + } + } else { + applyDefaultSpoofing(entityData, settings); + if (entityType == EntityTypes.PLAYER) { + spoofPlayerMetadata(entityData, settings); + } + } + } + + /** + * Applies default spoofing logic to common metadata. + */ + private void applyDefaultSpoofing(EntityData entityData, Settings settings) { + updateAirTicks(entityData, settings); + if (entityData.getIndex() == player.metadataIndex.HEALTH && settings.getEntityData().isHealth()) { + float health = (Float) entityData.getValue(); + if (health > 0) { + // Spoof health value to a fixed, low value. + entityData.setValue(0.5f); + } + } + } + + /** + * Applies gradual spoofing for iron golem health based on thresholds. + */ + private void spoofIronGolemMetadata(EntityData entityData, Settings settings) { + updateAirTicks(entityData, settings); + if (entityData.getIndex() == player.metadataIndex.HEALTH && settings.getEntityData().isHealth()) { + float health = (Float) entityData.getValue(); + if (health > IRON_GOLEM_THRESHOLD_1) { + entityData.setValue(IRON_GOLEM_HEALTH_MAX); + } else if (health > IRON_GOLEM_THRESHOLD_2) { + entityData.setValue(IRON_GOLEM_THRESHOLD_1); + } else if (health > IRON_GOLEM_THRESHOLD_3) { + entityData.setValue(IRON_GOLEM_THRESHOLD_2); + } else { + entityData.setValue(IRON_GOLEM_THRESHOLD_3); + } + } + } + + /** + * Spoofs player-specific metadata such as absorption and experience. + */ + private void spoofPlayerMetadata(EntityData entityData, Settings settings) { + if (entityData.getIndex() == player.metadataIndex.ABSORPTION && settings.getEntityData().isAbsorption()) { + setDynamicValue(entityData, 0); + } + if (entityData.getIndex() == player.metadataIndex.XP && settings.getEntityData().isXp()) { + setDynamicValue(entityData, 0); + } + } + + /** + * Updates the air ticks metadata if enabled in the settings. + */ + private void updateAirTicks(EntityData entityData, Settings settings) { + if (entityData.getIndex() == player.metadataIndex.AIR_TICKS && settings.getEntityData().isAirTicks()) { + setDynamicValue(entityData, 1); + } + } + + /** + * Sets a new value for the entity data while preserving its original numeric type. + *

+ * This method is necessary because the metadata value is stored as an {@code Object} + * and can be of different numeric types (e.g., Integer, Short, Byte, Long, Float, or Double). + * + * @param entityData The metadata object to modify. + * @param spoofValue The new value to set. + */ + private void setDynamicValue(EntityData entityData, int spoofValue) { + Object value = entityData.getValue(); + + if (value instanceof Integer) { + entityData.setValue(spoofValue); + } else if (value instanceof Short) { + entityData.setValue((short) spoofValue); + } else if (value instanceof Byte) { + entityData.setValue((byte) spoofValue); + } else if (value instanceof Long) { + entityData.setValue((long) spoofValue); + } else if (value instanceof Float) { + entityData.setValue((float) spoofValue); + } else if (value instanceof Double) { + entityData.setValue((double) spoofValue); + } + } +} diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/packetlisteners/spoofers/ScoreboardListener.java b/common/src/main/java/com/deathmotion/antihealthindicator/spoofers/impl/ScoreboardSpoofer.java similarity index 59% rename from common/src/main/java/com/deathmotion/antihealthindicator/packetlisteners/spoofers/ScoreboardListener.java rename to common/src/main/java/com/deathmotion/antihealthindicator/spoofers/impl/ScoreboardSpoofer.java index 415ac3e..bc90827 100644 --- a/common/src/main/java/com/deathmotion/antihealthindicator/packetlisteners/spoofers/ScoreboardListener.java +++ b/common/src/main/java/com/deathmotion/antihealthindicator/spoofers/impl/ScoreboardSpoofer.java @@ -1,27 +1,27 @@ /* - * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator - * Copyright (C) 2025 Bram and contributors + * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator + * Copyright (C) 2025 Bram and contributors * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * 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 General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ -package com.deathmotion.antihealthindicator.packetlisteners.spoofers; +package com.deathmotion.antihealthindicator.spoofers.impl; -import com.deathmotion.antihealthindicator.AHIPlatform; +import com.deathmotion.antihealthindicator.data.AHIPlayer; import com.deathmotion.antihealthindicator.data.Settings; -import com.deathmotion.antihealthindicator.managers.ConfigManager; -import com.github.retrooper.packetevents.event.PacketListenerAbstract; +import com.deathmotion.antihealthindicator.spoofers.Spoofer; +import com.deathmotion.antihealthindicator.spoofers.type.PacketSpoofer; import com.github.retrooper.packetevents.event.PacketSendEvent; import com.github.retrooper.packetevents.protocol.packettype.PacketType; import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerScoreboardObjective; @@ -31,21 +31,12 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -public class ScoreboardListener

extends PacketListenerAbstract { - private final ConfigManager

configManager; +public class ScoreboardSpoofer extends Spoofer implements PacketSpoofer { - // Use ConcurrentHashMap's KeySet for a thread-safe Set private final Set healthObjectives = ConcurrentHashMap.newKeySet(); - /** - * Constructs a new EntityMetadataListener with the specified {@link AHIPlatform}. - * - * @param platform The platform to use. - */ - public ScoreboardListener(AHIPlatform

platform) { - this.configManager = platform.getConfigManager(); - - platform.getLogManager().debug("Update Objective Listener initialized."); + public ScoreboardSpoofer(AHIPlayer player) { + super(player); } @Override @@ -91,4 +82,4 @@ private void handleUpdateScore(PacketSendEvent event) { event.markForReEncode(true); } } -} \ No newline at end of file +} diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/spoofers/impl/WorldSeedSpoofer.java b/common/src/main/java/com/deathmotion/antihealthindicator/spoofers/impl/WorldSeedSpoofer.java new file mode 100644 index 0000000..6d3c892 --- /dev/null +++ b/common/src/main/java/com/deathmotion/antihealthindicator/spoofers/impl/WorldSeedSpoofer.java @@ -0,0 +1,52 @@ +/* + * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator + * Copyright (C) 2025 Bram and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.deathmotion.antihealthindicator.spoofers.impl; + +import com.deathmotion.antihealthindicator.data.AHIPlayer; +import com.deathmotion.antihealthindicator.data.Settings; +import com.deathmotion.antihealthindicator.spoofers.Spoofer; +import com.deathmotion.antihealthindicator.spoofers.type.PacketSpoofer; +import com.github.retrooper.packetevents.event.PacketSendEvent; +import com.github.retrooper.packetevents.protocol.packettype.PacketType; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerJoinGame; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerRespawn; + +public class WorldSeedSpoofer extends Spoofer implements PacketSpoofer { + + public WorldSeedSpoofer(AHIPlayer player) { + super(player); + } + + @Override + public void onPacketSend(PacketSendEvent event) { + final Settings settings = configManager.getSettings(); + if (!settings.isWorldSeed()) return; + + if (event.getPacketType().equals(PacketType.Play.Server.JOIN_GAME)) { + WrapperPlayServerJoinGame wrapper = new WrapperPlayServerJoinGame(event); + wrapper.setHashedSeed(0L); + event.markForReEncode(true); + } + if (event.getPacketType().equals(PacketType.Play.Server.RESPAWN)) { + WrapperPlayServerRespawn wrapper = new WrapperPlayServerRespawn(event); + wrapper.setHashedSeed(0L); + event.markForReEncode(true); + } + } +} diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/spoofers/type/PacketSpoofer.java b/common/src/main/java/com/deathmotion/antihealthindicator/spoofers/type/PacketSpoofer.java new file mode 100644 index 0000000..3bdecb9 --- /dev/null +++ b/common/src/main/java/com/deathmotion/antihealthindicator/spoofers/type/PacketSpoofer.java @@ -0,0 +1,26 @@ +/* + * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator + * Copyright (C) 2025 Bram and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.deathmotion.antihealthindicator.spoofers.type; + +import com.github.retrooper.packetevents.event.PacketSendEvent; + +public interface PacketSpoofer { + default void onPacketSend(final PacketSendEvent event) { + } +} diff --git a/common/src/main/java/com/deathmotion/antihealthindicator/managers/UpdateManager.java b/common/src/main/java/com/deathmotion/antihealthindicator/util/UpdateChecker.java similarity index 66% rename from common/src/main/java/com/deathmotion/antihealthindicator/managers/UpdateManager.java rename to common/src/main/java/com/deathmotion/antihealthindicator/util/UpdateChecker.java index 2afc0be..0b51d47 100644 --- a/common/src/main/java/com/deathmotion/antihealthindicator/managers/UpdateManager.java +++ b/common/src/main/java/com/deathmotion/antihealthindicator/util/UpdateChecker.java @@ -1,35 +1,38 @@ /* - * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator - * Copyright (C) 2025 Bram and contributors + * This file is part of AntiHealthIndicator - https://github.com/Bram1903/AntiHealthIndicator + * Copyright (C) 2025 Bram and contributors * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * 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 General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ -package com.deathmotion.antihealthindicator.managers; +package com.deathmotion.antihealthindicator.util; import com.deathmotion.antihealthindicator.AHIPlatform; import com.deathmotion.antihealthindicator.api.versioning.AHIVersion; import com.deathmotion.antihealthindicator.data.Constants; import com.deathmotion.antihealthindicator.data.Settings; -import com.deathmotion.antihealthindicator.packetlisteners.UpdateNotifier; -import com.deathmotion.antihealthindicator.util.AHIVersions; -import com.github.retrooper.packetevents.PacketEvents; +import com.deathmotion.antihealthindicator.managers.LogManager; import com.google.gson.Gson; import com.google.gson.JsonObject; +import lombok.Getter; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import javax.annotation.Nullable; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -37,12 +40,16 @@ import java.net.URLConnection; import java.util.concurrent.CompletableFuture; -public class UpdateManager

{ +public class UpdateChecker

{ private final AHIPlatform

platform; private final Settings settings; private final LogManager

logManager; - public UpdateManager(AHIPlatform

platform) { + @Getter + private boolean updateAvailable = false; + private @Nullable AHIVersion latestVersion; + + public UpdateChecker(AHIPlatform

platform) { this.platform = platform; this.settings = platform.getConfigManager().getSettings(); this.logManager = platform.getLogManager(); @@ -56,7 +63,7 @@ public void checkForUpdate() { CompletableFuture.runAsync(() -> { try { AHIVersion localVersion = AHIVersions.CURRENT; - AHIVersion latestVersion = fetchLatestGitHubVersion(); + latestVersion = fetchLatestGitHubVersion(); if (latestVersion != null) { handleVersionComparison(localVersion, latestVersion); @@ -101,9 +108,8 @@ private void notifyUpdateAvailable(AHIVersion currentVersion, AHIVersion newVers .append(Component.text(" | New version: ", NamedTextColor.WHITE)) .append(Component.text(newVersion.toStringWithoutSnapshot(), NamedTextColor.DARK_PURPLE))); } - if (settings.getUpdateChecker().isNotifyInGame()) { - PacketEvents.getAPI().getEventManager().registerListener(new UpdateNotifier<>(platform, newVersion)); - } + + updateAvailable = true; } private void notifyOnDevBuild(AHIVersion currentVersion, AHIVersion newVersion) { @@ -116,4 +122,17 @@ private void notifyOnDevBuild(AHIVersion currentVersion, AHIVersion newVersion) .append(Component.text(newVersion.toStringWithoutSnapshot(), NamedTextColor.DARK_AQUA))); } } + + public Component getUpdateComponent() { + return Component.text() + .append(Component.text("[AntiHealthIndicator] ", NamedTextColor.RED) + .decoration(TextDecoration.BOLD, true)) + .append(Component.text("Version " + latestVersion.toStringWithoutSnapshot() + " is ", NamedTextColor.GREEN)) + .append(Component.text("now available", NamedTextColor.GREEN) + .decorate(TextDecoration.UNDERLINED) + .hoverEvent(HoverEvent.showText(Component.text("Click to download", NamedTextColor.GREEN))) + .clickEvent(ClickEvent.openUrl(Constants.MODRINTH_URL))) + .append(Component.text("!", NamedTextColor.GREEN)) + .build(); + } } \ No newline at end of file diff --git a/common/src/main/resources/config.yml b/common/src/main/resources/config.yml index 8b5f86d..691cfd9 100644 --- a/common/src/main/resources/config.yml +++ b/common/src/main/resources/config.yml @@ -20,10 +20,6 @@ update-checker: # When an update is found, should players with the permission "AntiHealthIndicator.Notify" be notified? notify-in-game: true -# If enabled, players with the permission "AntiHealthIndicator.Bypass" will not be affected by the spoofing. -allow-bypass: - enabled: false - # These options determine what information should be spoofed. spoof: # Sets the seed which is being sent to the client to zero. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 68265b4..9886238 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,6 +2,7 @@ adventure = "4.18.0" packetevents = "2.7.0" betterreload = "v1.0.0" +guava = "33.3.1-jre" paper = "1.21.4-R0.1-SNAPSHOT" velocity = "3.4.0-SNAPSHOT" bungeecord = "1.21-R0.1-SNAPSHOT" @@ -26,6 +27,7 @@ packetevents-sponge = { group = "com.github.retrooper", name = "packetevents-spo snakeyaml = { group = "org.yaml", name = "snakeyaml", version.ref = "snakeyaml" } lombok = { group = "org.projectlombok", name = "lombok", version.ref = "lombok" } betterreload-api = { group = "com.github.amnoah.betterreload", name = "api", version.ref = "betterreload" } +guava = { group = "com.google.guava", name = "guava", version.ref = "guava" } paper = { group = "io.papermc.paper", name = "paper-api", version.ref = "paper" } velocity = { group = "com.velocitypowered", name = "velocity-api", version.ref = "velocity" } bungeecord = { group = "net.md-5", name = "bungeecord-api", version.ref = "bungeecord" }