EntityLib/api/src/main/java/me/tofaa/entitylib/meta/EntityMeta.java
2024-11-14 19:37:33 +02:00

289 lines
9.7 KiB
Java

package me.tofaa.entitylib.meta;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.manager.server.VersionComparison;
import com.github.retrooper.packetevents.protocol.entity.data.EntityData;
import com.github.retrooper.packetevents.protocol.entity.data.EntityDataType;
import com.github.retrooper.packetevents.protocol.entity.data.EntityDataTypes;
import com.github.retrooper.packetevents.protocol.entity.data.EntityMetadataProvider;
import com.github.retrooper.packetevents.protocol.entity.pose.EntityPose;
import com.github.retrooper.packetevents.protocol.entity.type.EntityType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityMetadata;
import me.tofaa.entitylib.EntityLib;
import me.tofaa.entitylib.extras.InvalidVersionException;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiFunction;
import static me.tofaa.entitylib.meta.MetaOffsetConverter.EntityMetaOffsets.*;
public class EntityMeta implements EntityMetadataProvider {
private static final MetaConverterRegistry registry = new MetaConverterRegistry();
public static @NotNull BiFunction<Integer, Metadata, EntityMeta> getConverter(EntityType entityType) {
return registry.get(entityType);
}
public static @NotNull Class<? extends EntityMeta> getMetaClass(EntityType entityType) {
return registry.getMetaClass(entityType);
}
public static @NotNull EntityMeta createMeta(int entityId, EntityType entityType) {
Metadata metadata = new Metadata(entityId);
BiFunction<Integer, Metadata, EntityMeta> converter = getConverter(entityType);
return converter.apply(entityId, metadata);
}
public static final byte OFFSET = 0;
public static final byte MAX_OFFSET = OFFSET + 8;
private final static byte ON_FIRE_BIT = 0x01;
private final static byte CROUCHING_BIT = 0x02;
private final static byte SPRINTING_BIT = 0x08;
private final static byte SWIMMING_BIT = 0x10;
private final static byte INVISIBLE_BIT = 0x20;
private final static byte HAS_GLOWING_EFFECT_BIT = 0x40;
private final static byte FLYING_WITH_ELYTRA_BIT = (byte) 0x80;
protected final int entityId;
protected final Metadata metadata;
public EntityMeta(int entityId, Metadata metadata) {
this.entityId = entityId;
this.metadata = metadata;
}
public EntityMeta(EntityMeta other) {
this(other.entityId, new Metadata(other.entityId));
metadata.setMetaFromPacket(other.createPacket());
}
public EntityMeta(int entityId) {
this(entityId, new Metadata(entityId));
}
public void setNotifyAboutChanges(boolean notifyAboutChanges) {
metadata.setNotifyAboutChanges(notifyAboutChanges);
}
public boolean isNotifyingChanges() {
return metadata.isNotifyingChanges();
}
public boolean isOnFire() {
return getMaskBit(OFFSET, ON_FIRE_BIT);
}
public void setOnFire(boolean value) {
setMaskBit(OFFSET, ON_FIRE_BIT, value);
}
public boolean isSneaking() {
return getMaskBit(OFFSET, CROUCHING_BIT);
}
public void setSneaking(boolean value) {
setMaskBit(OFFSET, CROUCHING_BIT, value);
}
public boolean isSprinting() {
return getMaskBit(OFFSET, SPRINTING_BIT);
}
public void setSprinting(boolean value) {
setMaskBit(OFFSET, SPRINTING_BIT, value);
}
public boolean isInvisible() {
return getMaskBit(OFFSET, INVISIBLE_BIT);
}
public void setInvisible(boolean value) {
setMaskBit(OFFSET, INVISIBLE_BIT, value);
}
public boolean hasGlowingEffect() {
return getMaskBit(OFFSET, HAS_GLOWING_EFFECT_BIT);
}
public boolean isGlowing() {
return hasGlowingEffect();
}
public void setHasGlowingEffect(boolean value) {
setMaskBit(OFFSET, HAS_GLOWING_EFFECT_BIT, value);
}
public void setGlowing(boolean value) {
setHasGlowingEffect(value);
}
public boolean isSwimming() {
return getMaskBit(OFFSET, SWIMMING_BIT);
}
public void setSwimming(boolean value) {
setMaskBit(OFFSET, SWIMMING_BIT, value);
}
public boolean isFlyingWithElytra() {
return getMaskBit(OFFSET, FLYING_WITH_ELYTRA_BIT);
}
public void setFlyingWithElytra(boolean value) {
setMaskBit(OFFSET, FLYING_WITH_ELYTRA_BIT, value);
}
public short getAirTicks() {
return this.metadata.getIndex(airTicksOffset(), (short) 300);
}
public void setAirTicks(short value) {
this.metadata.setIndex(airTicksOffset(), EntityDataTypes.SHORT, value);
}
public Component getCustomName() {
Optional<Component> component = this.metadata.getIndex(customNameOffset(), Optional.empty());
return component.orElse(null);
}
public void setCustomName(Component value) {
this.metadata.setIndex(customNameOffset(), EntityDataTypes.OPTIONAL_ADV_COMPONENT, Optional.ofNullable(value));
}
public boolean isCustomNameVisible() {
return this.metadata.getIndex(customNameVisibleOffset(), false);
}
public void setCustomNameVisible(boolean value) {
this.metadata.setIndex(customNameVisibleOffset(), EntityDataTypes.BOOLEAN, value);
}
public boolean isSilent() {
return this.metadata.getIndex(silentOffset(), false);
}
public void setSilent(boolean value) {
this.metadata.setIndex(silentOffset(), EntityDataTypes.BOOLEAN, value);
}
public boolean hasNoGravity() {
return this.metadata.getIndex(hasNoGravityOffset(), true);
}
public void setHasNoGravity(boolean value) {
this.metadata.setIndex(hasNoGravityOffset(), EntityDataTypes.BOOLEAN, value);
}
public EntityPose getPose() {
return this.metadata.getIndex(poseOffset(), EntityPose.STANDING);
}
public void setPose(EntityPose value) {
this.metadata.setIndex(poseOffset(), EntityDataTypes.ENTITY_POSE, value);
}
public int getTicksFrozenInPowderedSnow() {
return this.metadata.getIndex(ticksFrozenInPowderedSnowOffset(), 0);
}
public void setTicksFrozenInPowderedSnow(int value) {
this.metadata.setIndex(ticksFrozenInPowderedSnowOffset(), EntityDataTypes.INT, value);
}
public WrapperPlayServerEntityMetadata createPacket() {
return metadata.createPacket();
}
protected static void isVersionNewer(ServerVersion version) {
if (EntityLib.getOptionalApi().isPresent()) {
if (!EntityLib.getApi().getPacketEvents().getServerManager().getVersion().is(VersionComparison.NEWER_THAN, version)) {
throw new InvalidVersionException("This method is only available for versions newer than " + version.name() + ".");
}
}
if (!PacketEvents.getAPI().getServerManager().getVersion().is(VersionComparison.NEWER_THAN, version)) {
throw new InvalidVersionException("This method is only available for versions newer than " + version.name() + ".");
}
}
protected static boolean isVersion(ServerVersion version, VersionComparison comparison) {
if (EntityLib.getOptionalApi().isPresent()) {
return EntityLib.getApi().getPacketEvents().getServerManager().getVersion().is(comparison, version);
}
return PacketEvents.getAPI().getServerManager().getVersion().is(comparison, version);
}
protected static boolean isVersion(ServerVersion version) {
if (EntityLib.getOptionalApi().isPresent()) {
return EntityLib.getApi().getPacketEvents().getServerManager().getVersion().is(VersionComparison.EQUALS, version);
}
return PacketEvents.getAPI().getServerManager().getVersion().is(VersionComparison.EQUALS, version);
}
/**
* Annoying java 8 not letting me do OFFSET + amount in the method call so this is a workaround
* @param value the value to offset
* @param amount the amount to offset by
* @return the offset value
*/
protected static byte offset(byte value, int amount) {
return (byte) (value + amount);
}
public <T> void setIndex(byte index, @NotNull EntityDataType<T> dataType, T value) {
this.metadata.setIndex(index, dataType, value);
}
public <T> T getIndex(byte index, @Nullable T defaultValue) {
return this.metadata.getIndex(index, defaultValue);
}
public byte getMask(byte index) {
return this.metadata.getIndex(index, (byte) 0);
}
public void setMask(byte index, byte mask) {
this.metadata.setIndex(index, EntityDataTypes.BYTE, mask);
}
public boolean getMaskBit(byte index, byte bit) {
return (getMask(index) & bit) == bit;
}
public void setMaskBit(int index, byte bit, boolean value) {
byte mask = getMask((byte)index);
boolean currentValue = (mask & bit) == bit;
if (currentValue == value) {
return;
}
if (value) {
mask |= bit;
} else {
mask &= (byte) ~bit;
}
setMask((byte)index, mask);
}
@Override
public List<EntityData> entityData(ClientVersion clientVersion) {
return metadata.getEntries(); // TODO: Atm this is useless cause of the way the api works. Might change in the future
}
@Override
public List<EntityData> entityData() {
return metadata.getEntries();
}
}