diff --git a/plugin/build.gradle b/plugin/build.gradle index 7cbf95f..ac8ee3d 100644 --- a/plugin/build.gradle +++ b/plugin/build.gradle @@ -30,10 +30,28 @@ dependencies { implementation project(":api") } +ext { + gitBranch = System.getenv('GIT_BRANCH') ?: '' + gitCommitHash = System.getenv('GIT_COMMIT') ?: '' + buildId = System.getenv('BUILD_ID') ?: '' +} + shadowJar { archivesBaseName = "ZNPCsPlus" archiveClassifier.set "" + manifest { + if (gitBranch?.trim()) { + attributes('Git-Branch': gitBranch) + } + if (gitCommitHash?.trim()) { + attributes('Git-Commit': gitCommitHash) + } + if (buildId?.trim()) { + attributes('Build-Id': buildId) + } + } + relocate "org.objectweb.asm", "lol.pyr.znpcsplus.libraries.asm" relocate "me.lucko.jarrelocator", "lol.pyr.znpcsplus.libraries.jarrelocator" diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/ZNpcsPlus.java b/plugin/src/main/java/lol/pyr/znpcsplus/ZNpcsPlus.java index 2a0d64f..2a12c2d 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/ZNpcsPlus.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/ZNpcsPlus.java @@ -342,6 +342,7 @@ public class ZNpcsPlus { .addSubcommand("delete", new ActionDeleteCommand(npcRegistry)) .addSubcommand("edit", new ActionEditCommand(npcRegistry, actionRegistry)) .addSubcommand("list", new ActionListCommand(npcRegistry))) + .addSubcommand("version", new VersionCommand(this)) ); } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/commands/VersionCommand.java b/plugin/src/main/java/lol/pyr/znpcsplus/commands/VersionCommand.java new file mode 100644 index 0000000..f59bf17 --- /dev/null +++ b/plugin/src/main/java/lol/pyr/znpcsplus/commands/VersionCommand.java @@ -0,0 +1,64 @@ +package lol.pyr.znpcsplus.commands; + +import lol.pyr.director.adventure.command.CommandContext; +import lol.pyr.director.adventure.command.CommandHandler; +import lol.pyr.director.common.command.CommandExecutionException; +import lol.pyr.znpcsplus.ZNpcsPlus; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.format.NamedTextColor; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.jar.Attributes; +import java.util.jar.JarFile; + +public class VersionCommand implements CommandHandler { + + private final String pluginVersion; + private final String gitBranch; + private final String gitCommitHash; + private final String buildId; + + public VersionCommand(ZNpcsPlus plugin) { + pluginVersion = plugin.getDescription().getVersion(); + String gitBranch = ""; + String gitCommitHash = ""; + String buildId = ""; + try { + URL jarUrl = getClass().getProtectionDomain().getCodeSource().getLocation(); + JarFile jarFile = new JarFile(jarUrl.toURI().getPath()); + Attributes attributes = jarFile.getManifest().getMainAttributes(); + gitBranch = attributes.getValue("Git-Branch"); + gitCommitHash = attributes.getValue("Git-Commit-Hash"); + buildId = attributes.getValue("Build-Id"); + } catch (IOException | URISyntaxException e) { + e.printStackTrace(); + } + this.gitBranch = gitBranch; + this.gitCommitHash = gitCommitHash; + this.buildId = buildId; + } + + @Override + public void run(CommandContext context) throws CommandExecutionException { + + StringBuilder versionBuilder = new StringBuilder("This server is running ZNPCsPlus version ").append(pluginVersion); + if (gitBranch != null && !gitBranch.isEmpty()) { + versionBuilder.append("-").append(gitBranch); + } + if (gitCommitHash != null && !gitCommitHash.isEmpty()) { + versionBuilder.append("@").append(gitCommitHash); + } + if (buildId != null && !buildId.isEmpty()) { + versionBuilder.append(" (Build #").append(buildId).append(")"); + } + + String version = versionBuilder.toString(); + + context.send(Component.text(version, NamedTextColor.GREEN) + .hoverEvent(Component.text("Click to copy version to clipboard")) + .clickEvent(ClickEvent.copyToClipboard(version))); + } +} diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/commands/property/PropertySetCommand.java b/plugin/src/main/java/lol/pyr/znpcsplus/commands/property/PropertySetCommand.java index 334d7f6..53a5e25 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/commands/property/PropertySetCommand.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/commands/property/PropertySetCommand.java @@ -11,6 +11,7 @@ import lol.pyr.director.adventure.command.CommandHandler; import lol.pyr.director.common.command.CommandExecutionException; import lol.pyr.znpcsplus.api.entity.EntityProperty; import lol.pyr.znpcsplus.entity.EntityPropertyImpl; +import lol.pyr.znpcsplus.entity.properties.attributes.AttributeProperty; import lol.pyr.znpcsplus.npc.NpcEntryImpl; import lol.pyr.znpcsplus.npc.NpcImpl; import lol.pyr.znpcsplus.npc.NpcRegistryImpl; @@ -124,6 +125,15 @@ public class PropertySetCommand implements CommandHandler { value = context.parse(type); valueName = value == null ? "NONE" : ((Vector3i) value).toPrettyString(); } + else if (property instanceof AttributeProperty) { + value = context.parse(type); + if ((double) value < ((AttributeProperty) property).getMinValue() || (double) value > ((AttributeProperty) property).getMaxValue()) { + double sanitizedValue = ((AttributeProperty) property).sanitizeValue((double) value); + context.send(Component.text("WARNING: Value " + value + " is out of range for property " + property.getName() + ", setting to " + sanitizedValue, NamedTextColor.YELLOW)); + value = sanitizedValue; + } + valueName = String.valueOf(value); + } else { try { value = context.parse(type); diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/entity/EntityPropertyRegistryImpl.java b/plugin/src/main/java/lol/pyr/znpcsplus/entity/EntityPropertyRegistryImpl.java index 8105e76..b4ba5b2 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/entity/EntityPropertyRegistryImpl.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/entity/EntityPropertyRegistryImpl.java @@ -2,6 +2,7 @@ package lol.pyr.znpcsplus.entity; import com.github.retrooper.packetevents.PacketEvents; import com.github.retrooper.packetevents.manager.server.ServerVersion; +import com.github.retrooper.packetevents.protocol.attribute.Attributes; import com.github.retrooper.packetevents.protocol.entity.data.EntityDataTypes; import com.github.retrooper.packetevents.protocol.entity.pose.EntityPose; import com.github.retrooper.packetevents.protocol.nbt.NBTCompound; @@ -15,6 +16,7 @@ import lol.pyr.znpcsplus.api.entity.EntityPropertyRegistry; import lol.pyr.znpcsplus.api.skin.SkinDescriptor; import lol.pyr.znpcsplus.config.ConfigManager; import lol.pyr.znpcsplus.entity.properties.*; +import lol.pyr.znpcsplus.entity.properties.attributes.AttributeProperty; import lol.pyr.znpcsplus.entity.properties.villager.VillagerLevelProperty; import lol.pyr.znpcsplus.entity.properties.villager.VillagerProfessionProperty; import lol.pyr.znpcsplus.entity.properties.villager.VillagerTypeProperty; @@ -154,6 +156,16 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry { linkProperties("glow", "fire", "invisible"); register(new BooleanProperty("silent", 4, false, legacyBooleans)); + // Attribute Max Health + register(new AttributeProperty(packetFactory, "attribute_max_health", Attributes.MAX_HEALTH)); + + // Health - LivingEntity + int healthIndex = 6; + if (ver.isNewerThanOrEquals(ServerVersion.V_1_17)) healthIndex = 9; + else if (ver.isNewerThanOrEquals(ServerVersion.V_1_14)) healthIndex = 8; + else if (ver.isNewerThanOrEquals(ServerVersion.V_1_10)) healthIndex = 7; + register(new HealthProperty(healthIndex)); + final int tameableIndex; if (ver.isNewerThanOrEquals(ServerVersion.V_1_17)) tameableIndex = 17; else if (ver.isNewerThanOrEquals(ServerVersion.V_1_15)) tameableIndex = 16; diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/entity/properties/HealthProperty.java b/plugin/src/main/java/lol/pyr/znpcsplus/entity/properties/HealthProperty.java new file mode 100644 index 0000000..82615c5 --- /dev/null +++ b/plugin/src/main/java/lol/pyr/znpcsplus/entity/properties/HealthProperty.java @@ -0,0 +1,27 @@ +package lol.pyr.znpcsplus.entity.properties; + +import com.github.retrooper.packetevents.protocol.attribute.Attributes; +import com.github.retrooper.packetevents.protocol.entity.data.EntityData; +import com.github.retrooper.packetevents.protocol.entity.data.EntityDataTypes; +import lol.pyr.znpcsplus.entity.EntityPropertyImpl; +import lol.pyr.znpcsplus.entity.PacketEntity; +import org.bukkit.entity.Player; + +import java.util.Map; + +public class HealthProperty extends EntityPropertyImpl { + private final int index; + + public HealthProperty(int index) { + super("health", 20f, Float.class); + this.index = index; + } + + @Override + public void apply(Player player, PacketEntity entity, boolean isSpawned, Map properties) { + float health = entity.getProperty(this); + health = (float) Attributes.MAX_HEALTH.sanitizeValue(health); + properties.put(index, new EntityData(index, EntityDataTypes.FLOAT, health)); + + } +} diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/entity/properties/attributes/AttributeProperty.java b/plugin/src/main/java/lol/pyr/znpcsplus/entity/properties/attributes/AttributeProperty.java new file mode 100644 index 0000000..30ae5af --- /dev/null +++ b/plugin/src/main/java/lol/pyr/znpcsplus/entity/properties/attributes/AttributeProperty.java @@ -0,0 +1,63 @@ +package lol.pyr.znpcsplus.entity.properties.attributes; + +import com.github.retrooper.packetevents.protocol.attribute.Attribute; +import com.github.retrooper.packetevents.protocol.entity.data.EntityData; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerUpdateAttributes; +import lol.pyr.znpcsplus.entity.EntityPropertyImpl; +import lol.pyr.znpcsplus.entity.PacketEntity; +import lol.pyr.znpcsplus.packets.PacketFactory; +import org.bukkit.entity.Player; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class AttributeProperty extends EntityPropertyImpl { + private final PacketFactory packetFactory; + private final Attribute attribute; + + public AttributeProperty(PacketFactory packetFactory, String name, Attribute attribute) { + super(name, attribute.getDefaultValue(), Double.class); + this.packetFactory = packetFactory; + this.attribute = attribute; + } + + public double getMinValue() { + return attribute.getMinValue(); + } + + public double getMaxValue() { + return attribute.getMaxValue(); + } + + public double sanitizeValue(double value) { + return attribute.sanitizeValue(value); + } + + @Override + public List applyStandalone(Player player, PacketEntity packetEntity, boolean isSpawned) { + apply(player, packetEntity, isSpawned, Collections.emptyList()); + return Collections.emptyList(); + } + + @Override + public void apply(Player player, PacketEntity entity, boolean isSpawned, Map properties) { + } + + public void apply(Player player, PacketEntity entity, boolean isSpawned, List properties) { + Double value = entity.getProperty(this); + if (value == null) { + return; + } + value = attribute.sanitizeValue(value); + if (isSpawned) { + packetFactory.sendAttribute(player, entity, new WrapperPlayServerUpdateAttributes.Property(attribute, value, Collections.emptyList())); + } else { + properties.add(new WrapperPlayServerUpdateAttributes.Property(attribute, value, Collections.emptyList())); + } + } + + public Attribute getAttribute() { + return attribute; + } +} diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcTypeImpl.java b/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcTypeImpl.java index 207e75c..b2eae03 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcTypeImpl.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcTypeImpl.java @@ -120,6 +120,9 @@ public class NpcTypeImpl implements NpcType { "player_knockback_horizontal", "player_knockback_cooldown", "player_knockback_sound", "player_knockback_sound_name", "player_knockback_sound_volume", "player_knockback_sound_pitch"); if (!type.equals(EntityTypes.PLAYER)) addProperties("dinnerbone"); + if (EntityTypes.isTypeInstanceOf(type, EntityTypes.LIVINGENTITY)) { + addProperties("health", "attribute_max_health"); + } // TODO: make this look nicer after completing the rest of the properties if (version.isNewerThanOrEquals(ServerVersion.V_1_9)) addProperties("glow"); if (version.isNewerThanOrEquals(ServerVersion.V_1_14)) { diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/packets/PacketFactory.java b/plugin/src/main/java/lol/pyr/znpcsplus/packets/PacketFactory.java index f2b58c1..1d3ba41 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/packets/PacketFactory.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/packets/PacketFactory.java @@ -2,6 +2,7 @@ package lol.pyr.znpcsplus.packets; import com.github.retrooper.packetevents.protocol.entity.data.EntityData; import com.github.retrooper.packetevents.protocol.player.Equipment; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerUpdateAttributes; import lol.pyr.znpcsplus.api.entity.PropertyHolder; import lol.pyr.znpcsplus.entity.PacketEntity; import lol.pyr.znpcsplus.util.NamedColor; @@ -25,4 +26,6 @@ public interface PacketFactory { void sendHeadRotation(Player player, PacketEntity entity, float yaw, float pitch); void sendHandSwing(Player player, PacketEntity entity, boolean offHand); void setPassengers(Player player, int vehicle, int... passengers); + void sendAllAttributes(Player player, PacketEntity entity, PropertyHolder properties); + void sendAttribute(Player player, PacketEntity entity, WrapperPlayServerUpdateAttributes.Property property); } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_17PacketFactory.java b/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_17PacketFactory.java index 45b6e41..5a97fbf 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_17PacketFactory.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_17PacketFactory.java @@ -1,6 +1,7 @@ package lol.pyr.znpcsplus.packets; import com.github.retrooper.packetevents.PacketEventsAPI; +import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes; import com.github.retrooper.packetevents.util.Vector3d; import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerSpawnEntity; import lol.pyr.znpcsplus.api.entity.PropertyHolder; @@ -27,6 +28,7 @@ public class V1_17PacketFactory extends V1_8PacketFactory { sendPacket(player, new WrapperPlayServerSpawnEntity(entity.getEntityId(), Optional.of(entity.getUuid()), entity.getType(), npcLocationToVector(location), location.getPitch(), location.getYaw(), location.getYaw(), 0, Optional.of(new Vector3d()))); sendAllMetadata(player, entity, properties); + if (EntityTypes.isTypeInstanceOf(entity.getType(), EntityTypes.LIVINGENTITY)) sendAllAttributes(player, entity, properties); createTeam(player, entity, properties.getProperty(propertyRegistry.getByName("glow", NamedColor.class))); } } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_20_2PacketFactory.java b/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_20_2PacketFactory.java index 9d5de17..4a760e4 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_20_2PacketFactory.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_20_2PacketFactory.java @@ -36,6 +36,7 @@ public class V1_20_2PacketFactory extends V1_19_3PacketFactory { npcLocationToVector(location), location.getPitch(), location.getYaw(), location.getYaw(), 0, Optional.of(new Vector3d()))); sendPacket(player, new WrapperPlayServerEntityHeadLook(entity.getEntityId(), location.getYaw())); sendAllMetadata(player, entity, properties); + sendAllAttributes(player, entity, properties); scheduler.runLaterAsync(() -> removeTabPlayer(player, entity), configManager.getConfig().tabHideDelay()); }); } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_8PacketFactory.java b/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_8PacketFactory.java index 3fe0504..e288aa2 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_8PacketFactory.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_8PacketFactory.java @@ -18,6 +18,7 @@ import lol.pyr.znpcsplus.config.ConfigManager; import lol.pyr.znpcsplus.entity.EntityPropertyImpl; import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl; import lol.pyr.znpcsplus.entity.PacketEntity; +import lol.pyr.znpcsplus.entity.properties.attributes.AttributeProperty; import lol.pyr.znpcsplus.scheduling.TaskScheduler; import lol.pyr.znpcsplus.skin.BaseSkinDescriptor; import lol.pyr.znpcsplus.util.NamedColor; @@ -55,6 +56,7 @@ public class V1_8PacketFactory implements PacketFactory { entity.getUuid(), npcLocationToVector(location), location.getYaw(), location.getPitch(), Collections.emptyList())); sendPacket(player, new WrapperPlayServerEntityHeadLook(entity.getEntityId(), location.getYaw())); sendAllMetadata(player, entity, properties); + sendAllAttributes(player, entity, properties); scheduler.runLaterAsync(() -> removeTabPlayer(player, entity), configManager.getConfig().tabHideDelay()); }); } @@ -70,6 +72,7 @@ public class V1_8PacketFactory implements PacketFactory { new WrapperPlayServerSpawnEntity(entity.getEntityId(), Optional.of(entity.getUuid()), entity.getType(), npcLocationToVector(location), location.getPitch(), location.getYaw(), location.getYaw(), 0, Optional.empty())); sendAllMetadata(player, entity, properties); + if (EntityTypes.isTypeInstanceOf(type, EntityTypes.LIVINGENTITY)) sendAllAttributes(player, entity, properties); createTeam(player, entity, properties.getProperty(propertyRegistry.getByName("glow", NamedColor.class))); } @@ -187,4 +190,19 @@ public class V1_8PacketFactory implements PacketFactory { WrapperPlayServerEntityAnimation.EntityAnimationType.SWING_OFF_HAND : WrapperPlayServerEntityAnimation.EntityAnimationType.SWING_MAIN_ARM)); } + + @Override + public void sendAllAttributes(Player player, PacketEntity entity, PropertyHolder properties) { + List attributesList = new ArrayList<>(); + properties.getAppliedProperties() + .stream() + .filter(property -> property instanceof AttributeProperty) + .forEach(property -> ((AttributeProperty) property).apply(player, entity, false, attributesList)); + sendPacket(player, new WrapperPlayServerUpdateAttributes(entity.getEntityId(), attributesList)); + } + + @Override + public void sendAttribute(Player player, PacketEntity entity, WrapperPlayServerUpdateAttributes.Property property) { + sendPacket(player, new WrapperPlayServerUpdateAttributes(entity.getEntityId(), Collections.singletonList(property))); + } }