├── .gitignore ├── LICENSE ├── POM.xml ├── README.md └── src └── main ├── java └── com │ └── lkeehl │ └── tagapi │ ├── TagAPI.java │ ├── TagBuilder.java │ ├── TagListener.java │ ├── TagTracker.java │ ├── api │ ├── Tag.java │ ├── TagEntity.java │ └── TagLine.java │ ├── manager │ └── TagManager.java │ ├── tags │ ├── BaseTag.java │ ├── BaseTagEntity.java │ └── BaseTagLine.java │ ├── util │ ├── SetMap.java │ ├── TagUtil.java │ ├── VersionFile.java │ └── WatcherType.java │ └── wrappers │ ├── AbstractPacket.java │ ├── AbstractSpawnPacket.java │ ├── DevPacket.java │ ├── Wrappers.java │ └── versions │ ├── Wrapper1_17_1.java │ ├── Wrapper1_18_1.java │ └── Wrapper1_19_1.java └── resources ├── plugin.yml └── versions.cult /.gitignore: -------------------------------------------------------------------------------- 1 | # User-specific stuff 2 | .idea/ 3 | 4 | *.iml 5 | *.ipr 6 | *.iws 7 | 8 | # IntelliJ 9 | out/ 10 | 11 | # Compiled class file 12 | *.class 13 | 14 | # Log file 15 | *.log 16 | 17 | # BlueJ files 18 | *.ctxt 19 | 20 | # Package Files # 21 | *.jar 22 | *.war 23 | *.nar 24 | *.ear 25 | *.zip 26 | *.tar.gz 27 | *.rar 28 | 29 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 30 | hs_err_pid* 31 | 32 | *~ 33 | 34 | # temporary files which can be created if a process still has a handle open of a deleted file 35 | .fuse_hidden* 36 | 37 | # KDE directory preferences 38 | .directory 39 | 40 | # Linux trash folder which might appear on any partition or disk 41 | .Trash-* 42 | 43 | # .nfs files are created when an open file is removed but is still being accessed 44 | .nfs* 45 | 46 | # General 47 | .DS_Store 48 | .AppleDouble 49 | .LSOverride 50 | 51 | # Icon must end with two \r 52 | Icon 53 | 54 | # Thumbnails 55 | ._* 56 | 57 | # Files that might appear in the root of a volume 58 | .DocumentRevisions-V100 59 | .fseventsd 60 | .Spotlight-V100 61 | .TemporaryItems 62 | .Trashes 63 | .VolumeIcon.icns 64 | .com.apple.timemachine.donotpresent 65 | 66 | # Directories potentially created on remote AFP share 67 | .AppleDB 68 | .AppleDesktop 69 | Network Trash Folder 70 | Temporary Items 71 | .apdisk 72 | 73 | # Windows thumbnail cache files 74 | Thumbs.db 75 | Thumbs.db:encryptable 76 | ehthumbs.db 77 | ehthumbs_vista.db 78 | 79 | # Dump file 80 | *.stackdump 81 | 82 | # Folder config file 83 | [Dd]esktop.ini 84 | 85 | # Recycle Bin used on file shares 86 | $RECYCLE.BIN/ 87 | 88 | # Windows Installer files 89 | *.cab 90 | *.msi 91 | *.msix 92 | *.msm 93 | *.msp 94 | 95 | # Windows shortcuts 96 | *.lnk 97 | 98 | target/ 99 | javadocs/ 100 | 101 | #Gradle 102 | .gradle/ 103 | build/ 104 | 105 | # Common working directory 106 | run/ 107 | /src/main/java/com/lkeehl/tagapi/manager/ 108 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Keehl 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /POM.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.lkeehl 8 | tagapi 9 | 1.2.3 10 | 11 | src/main/java 12 | 13 | 14 | src/main/resources 15 | 16 | **/*.java 17 | LICENSE 18 | README.md 19 | 20 | 21 | 22 | 23 | 24 | org.apache.maven.plugins 25 | maven-compiler-plugin 26 | 27 | 16 28 | 16 29 | 30 | 31 | 32 | org.apache.maven.plugins 33 | maven-source-plugin 34 | 2.2.1 35 | 36 | 37 | attach-sources 38 | 39 | jar-no-fork 40 | 41 | 42 | 43 | 44 | 45 | org.apache.maven.plugins 46 | maven-javadoc-plugin 47 | 2.9.1 48 | 49 | 50 | 51 | jar 52 | 53 | 54 | 55 | 56 | 57 | org.apache.maven.plugins 58 | maven-gpg-plugin 59 | 1.5 60 | 61 | 62 | sign-artifacts 63 | verify 64 | 65 | sign 66 | 67 | 68 | 69 | 70 | 71 | org.sonatype.plugins 72 | nexus-staging-maven-plugin 73 | 1.6.8 74 | true 75 | 76 | ossrh 77 | https://s01.oss.sonatype.org/ 78 | true 79 | 80 | --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED 81 | --add-opens=java.base/java.text=ALL-UNNAMED --add-opens=java.desktop/java.awt.font=ALL-UNNAMED 82 | 83 | 84 | 85 | 90 | 91 | com.thoughtworks.xstream 92 | xstream 93 | 1.4.15 94 | 95 | 96 | 97 | 98 | 99 | jar 100 | 101 | ${project.groupId}:${project.artifactId} 102 | A Spigot resource that allows developers to easily create extra lines above entities heads. 103 | 104 | https://lkeehl.com/ 105 | 106 | 107 | 108 | MIT License 109 | http://www.opensource.org/licenses/mit-license.php 110 | 111 | 112 | 113 | 114 | 115 | ossrh 116 | https://s01.oss.sonatype.org/content/repositories/snapshots 117 | 118 | 119 | ossrh 120 | https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ 121 | 122 | 123 | 124 | 125 | UTF-8 126 | 1.17.1-R0.1-SNAPSHOT 127 | 128 | 129 | 130 | 131 | Lane Keehl 132 | lanekeehl@gmail.com 133 | Lane Keehl 134 | https://lkeehl.com/ 135 | 136 | 137 | 138 | 139 | scm:git:git://github.com/keehl254/TagAPI.git 140 | scm:git:ssh://github.com:keehl254/TagAPI.git 141 | https://github.com/keehl254/TagAPI/tree/master 142 | 143 | 144 | 145 | 146 | spigot-repo 147 | https://hub.spigotmc.org/nexus/content/repositories/snapshots/ 148 | 149 | 150 | dmulloy2-repo 151 | https://repo.dmulloy2.net/repository/public/ 152 | 153 | 154 | 155 | 156 | 157 | junit 158 | junit 159 | 4.13.2 160 | test 161 | 162 | 163 | org.spigotmc 164 | spigot-api 165 | ${spigot.version} 166 | provided 167 | 168 | 169 | org.spigotmc 170 | spigot 171 | ${spigot.version} 172 | provided 173 | 174 | 175 | com.comphenix.protocol 176 | ProtocolLib 177 | 4.8.0 178 | provided 179 | 180 | 181 | org.eclipse.jdt 182 | org.eclipse.jdt.annotation 183 | 2.2.600 184 | provided 185 | 186 | 187 | 188 | 189 | 190 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TagAPI (Dropped Project for now) 2 | A picture of a player with a custom title under their name 3 | I have frequently come across the question from developers on both the Spigot forums and discord servers that asks how to create customizable text under a players name. A system that provides unlockable "titles" under a players name has been a staple in a server for over two years that I have developed for, and I wished to share the method which I use to create them. 4 |
5 | There have been a couple of methods I had seen to accomplish this: 6 |

7 | 12 | 13 |
14 | Show Method Differences 15 |

Scoreboards

16 | 17 |

18 | Scoreboards seem like the easiest solution to this issue, but it has quite a few drawbacks, such as requiring a number next to the text and a maximum of one line is available to work with. You will also constantly being fighting with other plugins over the players current scoreboard. 19 |

20 |

21 |

Armor Stand Teleportation

22 | This method, as well as the following one, are the go-to methods for creating multiple tags above an entity. You can have an unlimited number of them and, for the most part, will not have to worry about support from other plugins. Compared to the mount technique, you will not have to worry about setting passengers of the entity; however, this method does have it's own drawback: Trailing. The tags will appear to follow the player rather than be attached to them. This can seemingly be fixed for players specifically by listening to the move packets rather, but other entities will trail. 23 |
24 |

Mount

25 | Finally, we reach the technique that I use in this resource. The Mount method attaches the tags to the entity by having them ride the entity. Minecraft's own client will handle all the movement, so there will be no trailing like with the teleportation method. On top of this benefit, there are a significant less amount of packets sent to the nearby players. While the teleport has to send teleport packets for every entity to every nearby player every time the target moves, the mount method only requires 4 packets: 26 |

27 | 33 |
34 | These packets will only be sent to the nearby players whenever needed. The downside of using the Mount method is that another plugin setting the passenger of the target entity will cause the tag to disappear or pop-off. 35 |
36 |
This API only supports the Mounting technique. 37 |
38 |

Uses

39 | There are many great uses for a resource such as this, but I wanted to compile a list of ways that this may help you. 40 | 46 |

Client Side Only

47 | Yep! It's true! This API does not create any actual entities to work. The entirety of it uses packets to trick the client into thinking an entity exists. What are the benefits, though? Well, there are quite a few.

First off, no messes. The last thing a server owner would like is for a plugin to incorrectly shutdown, and now there are random floating tags around the world where entities once were. With the entirety of this system using packets and only existing on the clients end, you will not have to worry about this ever! 48 |

Second, and my personal favorite, is per-player customization. You can have the lines of the tags appear different to each individual player, or even hide specific lines from individual players. 49 | 50 |

How To Install On a Server

51 | Easy! There are two ways you can use this resource. This resource can either be downloaded from the SpigotMC Plugin Page as an individual plugin to be dropped into the servers plugins directory, or you can directly integrate it into your code using maven or github download. 52 | 53 | 54 | com.lkeehl 55 | tagapi 56 | 1.2.3 57 | 58 | 59 |
If you are directly integrating this API into your own code, then the methods to enable and disable it are simple: 60 | In your plugins onEnable, include a line such as 61 |
62 | 63 | TagAPI.onEnable(this); 64 |
65 | and onDisable: 66 |
67 | 68 | TagAPI.onDisable(); 69 |
70 |

Creating Tags

71 | Tags can be created by using the Builder or Tag.create(entity) method like so: 72 |
73 | 74 | TagBuilder builder = TagBuilder.create(entity); // Create a new TagBuilder 75 | builder.withLine(pl->"First Line").withLine(pl->"Second Line"); // Add a first and second line 76 | builder.withLine(pl->"Third Line", pl->false); // Add another line, because why not? 77 | builder.build(); // Build will return a Tag instance, which you can use with methods later in this page 78 | 79 | // or 80 | 81 | Tag tag = Tag.create(entity); // Create a new Tag 82 | tag.addTagLine(10).setText(pl->"First Line"); 83 | tag.addTagLine(9).setText(pl->"Second Line"); 84 | tag.addTagLine(8).setText(pl->"Third Line").setKeepSpaceWhenNull(pl -> false); 85 | 86 |
87 | For each TagLine, you are able to customize two settings: What the line should return and whether the line should be visible if the line is null. In the TagBuilder, the line text is the first argument in the withLine method whereas the keepSpaceWhenNull setting an optional second argument. 88 |
89 | In the second example, the addTagLine method returns a TagLine object, which you can directly call .setGameName and .setKeepSpaceWhenNull to alter the aforementioned settings. 90 |

91 | Using the traditional Tag.create method, you can see that the addTagLine method accepts an integer. This is the priority towards the top. The TagBuilder does not have this, as the order of the lines will be the order you add them to the builder. 92 |

Getting, Updating, and Removing Tags

93 | From here, everything can be controlled through the Tag or TagAPI class like so: 94 | 95 | Tag tag = Tag.create(entity); // Create a tag for the entity 96 | tag.giveTag(); // Give the entity the tag. 97 | 98 | tag = TagAPI.getTag(entity); // Grab the tag of an entity. Entity must have been given the tag using above method. 99 | tag.updateTag(); // Update the tag incase lines should change for viewers 100 | tag.removeTag(); // Remove the tag from the entity. This tag can still be given back to the entity later. 101 | 102 | The above example shows creating a new tag for an entity, spawning the tag in, grabbing an entities tag, updating the tag, as well as removing one. 103 | The same can be accomplished through the TagAPI class. 104 | 105 | TagAPI.giveTag(entity, tagConstructor); // Create and give tag to entity. tagConstructor is a Function that receives an entity and returns a tag. 106 | TagAPI.updateTag(entity); // Update the entities tag 107 | TagAPI.removeTag(entity); // Remove a tag from an entity 108 | 109 |

Setting Default Tags

110 | I lastly wanted to share that it is possible to set default tags for entity types using the method in the following example: 111 | 112 | TagAPI.setDefaultTag(EntityType.PIG, target -> 113 | TagBuilder.create(target).withLine(pl -> target.getName()).withLine(pl -> ChatColor.YELLOW + "Hello " + pl.getName() + "!").build() 114 | ); 115 | 116 |

117 | 118 | All viewed entities will be checked only once if they should have a tag. This means that you can easily remove a tag from an entity, and it will not respawn itself. 119 |

Dependencies

120 | TagAPI requires ProtocolLib to run, and will only run in version 1.17.1 or higher as of the current release. 121 | 122 | -------------------------------------------------------------------------------- /src/main/java/com/lkeehl/tagapi/TagAPI.java: -------------------------------------------------------------------------------- 1 | package com.lkeehl.tagapi; 2 | 3 | import com.lkeehl.tagapi.api.Tag; 4 | import com.lkeehl.tagapi.tags.BaseTagEntity; 5 | import com.lkeehl.tagapi.util.VersionFile; 6 | import com.lkeehl.tagapi.wrappers.versions.Wrapper1_17_1; 7 | import com.lkeehl.tagapi.wrappers.versions.Wrapper1_18_1; 8 | import com.lkeehl.tagapi.wrappers.versions.Wrapper1_19_1; 9 | import org.bukkit.Bukkit; 10 | import org.bukkit.entity.Entity; 11 | import org.bukkit.entity.EntityType; 12 | import org.bukkit.entity.Player; 13 | import org.bukkit.event.HandlerList; 14 | import org.bukkit.plugin.IllegalPluginAccessException; 15 | import org.bukkit.plugin.java.JavaPlugin; 16 | 17 | import java.util.HashMap; 18 | import java.util.Map; 19 | import java.util.function.Function; 20 | 21 | public class TagAPI { 22 | 23 | protected static JavaPlugin plugin; 24 | 25 | private static TagTracker tagTracker; 26 | private static TagListener listener; 27 | 28 | protected static Map> entityDefaultTags = new HashMap<>(); 29 | 30 | /** 31 | * Initiates the TagAPI 32 | * 33 | * @param javaPlugin A plugin is required to activate TagAPI so that registers and packet wrappers may work. 34 | */ 35 | public static void onEnable(JavaPlugin javaPlugin) { 36 | TagAPI.plugin = javaPlugin; 37 | 38 | VersionFile versionFile = new VersionFile(); 39 | BaseTagEntity.init(versionFile); 40 | 41 | String name = Bukkit.getServer().getClass().getPackage().getName(); 42 | String version = name.substring(name.lastIndexOf('.') + 1); 43 | 44 | if ("v1_17_R1".equals(version)) 45 | Wrapper1_17_1.init(versionFile); 46 | else if (version.startsWith("v1_18")) 47 | Wrapper1_18_1.init(versionFile); 48 | else 49 | Wrapper1_19_1.init(versionFile); 50 | 51 | tagTracker = new TagTracker(); 52 | Bukkit.getPluginManager().registerEvents(listener = new TagListener(), javaPlugin); 53 | } 54 | 55 | /** 56 | * Disables the listener and destroys all active tags 57 | */ 58 | public static void onDisable() { 59 | listener.onDisable(); 60 | HandlerList.unregisterAll(listener); 61 | try { 62 | tagTracker.destroyAll(); 63 | } catch (IllegalPluginAccessException ignored) { 64 | } 65 | } 66 | 67 | /** 68 | * Returns the plugin used to activate TagAPI 69 | * 70 | * @return the plugin used to activate TagAPI 71 | */ 72 | public static JavaPlugin getPlugin() { 73 | return plugin; 74 | } 75 | 76 | /** 77 | * Returns a class used by TagAPI to track existing tags and their target entities. 78 | * 79 | * @return TagAPI's tag tracker instance 80 | */ 81 | public static TagTracker getTagTracker() { 82 | return tagTracker; 83 | } 84 | 85 | /** 86 | * Returns whether or not an entity has a tag 87 | * 88 | * @param entity The entity that will be checked for a tag 89 | * @return a boolean depicting if an entity has a tag 90 | */ 91 | public static boolean hasTag(Entity entity) { 92 | return tagTracker.getEntityTag(entity.getEntityId()) != null; 93 | } 94 | 95 | /** 96 | * Returns the tag, if any, that an entity has. Returns null if 97 | * the entity does not have a tag. 98 | * 99 | * @param entity The entity that will be checked for a tag 100 | * @return the tag belonging to the entity 101 | */ 102 | public static Tag getTag(Entity entity) { 103 | return tagTracker.getEntityTag(entity.getEntityId()); 104 | } 105 | 106 | /** 107 | * Adds or replaces the tag for the tags target 108 | * 109 | * @param entity The entity that the tag should be applied to 110 | * @param tagConstructor The constructor for a tag to be added to an entity 111 | */ 112 | public static void giveTag(Entity entity, Function tagConstructor) { 113 | tagConstructor.apply(entity).giveTag(); 114 | } 115 | 116 | /** 117 | * Removes the tag for a provided entity 118 | * 119 | * @param entity The entity that the tag should be provided for 120 | */ 121 | public static void removeTag(Entity entity) { 122 | Tag tag = tagTracker.getEntityTag(entity.getEntityId()); 123 | if (tag != null) 124 | tag.removeTag(); 125 | } 126 | 127 | /** 128 | * Updates the tag of an entity for all players. This will 129 | * refresh lines for players, as well as remove/add any lines that 130 | * should or should not be visible for individual players. 131 | * 132 | * @param entity The entity whose tag will be updated 133 | */ 134 | public static void updateTag(Entity entity) { 135 | Tag tag = getTag(entity); 136 | if (tag == null) 137 | return; 138 | tag.updateTag(); 139 | } 140 | 141 | /** 142 | * Updates the tag of an entity for a specified player. This will 143 | * refresh lines for the player, as well as remove/add any lines that 144 | * should or should not be visible for the specified player. 145 | * 146 | * @param entity The entity whose tag will be updated 147 | * @param viewer The player that the tag should be updated for 148 | */ 149 | public static void updateTagFor(Entity entity, Player viewer) { 150 | Tag tag = getTag(entity); 151 | if (tag == null) 152 | return; 153 | tag.updateTagFor(viewer); 154 | } 155 | 156 | /** 157 | * Adding a default tag for an entity type will provide all entities 158 | * of the given type a tag upon becoming visible in the world to a 159 | * player. If the entity has already had a tag, it will not re-add 160 | * the tag. 161 | * 162 | * @param type The entity type the tag will be applied to 163 | * @param tagConstructor A function that will create a tag for a provided entity. 164 | */ 165 | public static void setDefaultTag(EntityType type, Function tagConstructor) { 166 | entityDefaultTags.put(type, tagConstructor); 167 | } 168 | 169 | /** 170 | * Calling this method will allow the packet listener to listen for player and entity movement to ensure 171 | * a tag is hidden if a player is outside an appropriate distance from the viewer. 172 | */ 173 | public static void listenForMovement() { 174 | listener.listenForMovement(); 175 | } 176 | 177 | } 178 | -------------------------------------------------------------------------------- /src/main/java/com/lkeehl/tagapi/TagBuilder.java: -------------------------------------------------------------------------------- 1 | package com.lkeehl.tagapi; 2 | 3 | import com.lkeehl.tagapi.api.Tag; 4 | import com.lkeehl.tagapi.tags.BaseTag; 5 | import org.bukkit.entity.Entity; 6 | import org.bukkit.entity.Player; 7 | 8 | import java.util.function.Function; 9 | 10 | public class TagBuilder { 11 | 12 | private final Tag tag; 13 | private int priority = Integer.MAX_VALUE; 14 | 15 | private TagBuilder(Entity entity) { 16 | this.tag = BaseTag.create(entity); 17 | } 18 | 19 | /** 20 | * Creates a line with a provided Player -> String line function. 21 | * 22 | * @param textFunction A Function that determines what the line should say to the provided player. 23 | * @return This tag builder. 24 | */ 25 | public TagBuilder withLine(Function textFunction) { 26 | this.tag.addTagLine(priority--).setText(textFunction); 27 | return this; 28 | } 29 | 30 | /** 31 | * Creates a line with a provided Player -> String line function as well as a Player -> Boolean for null space checks. 32 | * 33 | * @param textFunction A Function that determines what the line should say to the provided player. 34 | * @param keepSpaceWhenNull A Function that determines whether the line should be visible if the line is null to the provided player. 35 | * @return This tag builder. 36 | */ 37 | public TagBuilder withLine(Function textFunction, Function keepSpaceWhenNull) { 38 | this.tag.addTagLine(priority--).setText(textFunction).setKeepSpaceWhenNull(keepSpaceWhenNull); 39 | return this; 40 | } 41 | 42 | /** 43 | * Creates a Tag out of this TagBuilder. 44 | * 45 | * @return A tag instance using the data from this Builder. 46 | */ 47 | public Tag build() { 48 | return this.tag; 49 | } 50 | 51 | /** 52 | * Creates a TagBuilder from a provided entity. 53 | * 54 | * @param entity The entity that this tag will belong to. 55 | * @return A tag builder used to create a tag for the provided entity. 56 | */ 57 | public static TagBuilder create(Entity entity) { 58 | assert entity != null; 59 | return new TagBuilder(entity); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/lkeehl/tagapi/TagListener.java: -------------------------------------------------------------------------------- 1 | package com.lkeehl.tagapi; 2 | 3 | import com.comphenix.protocol.PacketType; 4 | import com.comphenix.protocol.ProtocolLibrary; 5 | import com.comphenix.protocol.ProtocolManager; 6 | import com.comphenix.protocol.events.ListenerPriority; 7 | import com.comphenix.protocol.events.PacketAdapter; 8 | import com.comphenix.protocol.events.PacketEvent; 9 | import com.comphenix.protocol.wrappers.EnumWrappers; 10 | import com.comphenix.protocol.wrappers.PlayerInfoData; 11 | import com.comphenix.protocol.wrappers.WrappedWatchableObject; 12 | import com.lkeehl.tagapi.api.TagLine; 13 | import com.lkeehl.tagapi.tags.BaseTag; 14 | import com.lkeehl.tagapi.tags.BaseTagEntity; 15 | import com.lkeehl.tagapi.util.TagUtil; 16 | import com.lkeehl.tagapi.wrappers.AbstractPacket; 17 | import com.lkeehl.tagapi.wrappers.Wrappers; 18 | import org.bukkit.Bukkit; 19 | import org.bukkit.World; 20 | import org.bukkit.entity.Entity; 21 | import org.bukkit.entity.EntityType; 22 | import org.bukkit.entity.LivingEntity; 23 | import org.bukkit.entity.Player; 24 | import org.bukkit.event.EventHandler; 25 | import org.bukkit.event.EventPriority; 26 | import org.bukkit.event.Listener; 27 | import org.bukkit.event.entity.EntityDeathEvent; 28 | import org.bukkit.event.player.PlayerJoinEvent; 29 | import org.bukkit.event.player.PlayerQuitEvent; 30 | import org.bukkit.event.world.ChunkUnloadEvent; 31 | import org.bukkit.metadata.FixedMetadataValue; 32 | 33 | import java.util.ArrayList; 34 | import java.util.List; 35 | import java.util.Optional; 36 | 37 | import static com.comphenix.protocol.PacketType.Play.Server.*; 38 | 39 | public class TagListener implements Listener { 40 | 41 | private final PacketAdapter sendAdapter; 42 | private final PacketAdapter receiveAdapter; 43 | 44 | private boolean listenForMovement = false; 45 | 46 | private int taskID = -1; 47 | 48 | private final List initiatingEntitiesToIgnore = new ArrayList<>(); 49 | 50 | public TagListener() { 51 | 52 | /* 53 | The send adapter listens for entity spawning and destroying packets being sent to a player 54 | so that TagAPI can properly create or destroy tags related to the created/destroyed entity. 55 | 56 | It would be frustrating having tags left over for a player who died or left the server. It 57 | would be equally frustrating having tags not reappear if a player leaves view distance and 58 | comes back. 59 | */ 60 | this.sendAdapter = new PacketAdapter(TagAPI.getPlugin(), ListenerPriority.MONITOR, Wrappers.packetTypes) { 61 | @Override 62 | public void onPacketSending(PacketEvent event) { 63 | PacketType packetType = event.getPacketType(); 64 | if (packetType == SPAWN_ENTITY || packetType == SPAWN_ENTITY_LIVING || packetType == NAMED_ENTITY_SPAWN) { 65 | int entityID = event.getPacket().getIntegers().read(0); 66 | if (TagAPI.getTagTracker().isTagEntity(entityID)) 67 | return; 68 | 69 | BaseTag tag = (BaseTag) TagAPI.getTagTracker().getEntityTag(entityID); 70 | if (tag == null) { 71 | if (initiatingEntitiesToIgnore.contains(entityID)) 72 | return; 73 | initiatingEntitiesToIgnore.add(entityID); 74 | Bukkit.getScheduler().runTaskLater(TagAPI.getPlugin(), () -> createTagForSpawnedEntity(entityID, event.getPlayer().getWorld()), 1L); 75 | return; 76 | } 77 | 78 | if (packetType == NAMED_ENTITY_SPAWN) 79 | tag.unregisterViewer(event.getPlayer()); 80 | 81 | tag.spawnTagFor(event.getPlayer()); 82 | } else if (packetType.equals(ENTITY_METADATA)) { 83 | Wrappers.MetaDataPacket wrapper = Wrappers.METADATA_W_CONTAINER.apply(event.getPacket()); 84 | int entityID = event.getPacket().getIntegers().read(0); 85 | if (TagAPI.getTagTracker().isTagEntity(entityID)) 86 | return; 87 | BaseTag tag = (BaseTag) TagAPI.getTagTracker().getEntityTag(entityID); 88 | if (tag == null) 89 | return; 90 | Optional baseEntityData = wrapper.getMetadata().stream().filter(i -> i.getIndex() == 0).findFirst(); 91 | if (baseEntityData.isEmpty()) 92 | return; 93 | byte value = (byte) baseEntityData.get().getValue(); 94 | if ((value & 34) == 0) { 95 | tag.updateTagFor(event.getPlayer(), true, false); 96 | return; 97 | } 98 | tag.updateTagFor(event.getPlayer(), (value & 32) == 0, (value & 2) != 0); 99 | } else if (packetType.equals(PLAYER_INFO)) { 100 | EnumWrappers.PlayerInfoAction action = event.getPacket().getPlayerInfoAction().read(0); 101 | if (action == EnumWrappers.PlayerInfoAction.REMOVE_PLAYER || action == EnumWrappers.PlayerInfoAction.ADD_PLAYER) { 102 | List dataList = event.getPacket().getPlayerInfoDataLists().read(0); 103 | for (PlayerInfoData data : dataList) { 104 | Player player = Bukkit.getPlayer(data.getProfile().getUUID()); 105 | if (player == null || !player.isOnline()) 106 | continue; 107 | BaseTag tag = (BaseTag) TagAPI.getTagTracker().getEntityTag(player.getEntityId()); 108 | if (tag == null) 109 | continue; 110 | if (action == EnumWrappers.PlayerInfoAction.REMOVE_PLAYER) 111 | tag.destroyTagFor(event.getPlayer()); 112 | else 113 | tag.updateTagFor(event.getPlayer()); 114 | } 115 | } 116 | } else if (packetType.equals(ENTITY_DESTROY)) { 117 | Wrappers.DestroyPacket wrapper = Wrappers.DESTROY_W_CONTAINER.apply(event.getPacket()); 118 | for (int entityID : wrapper.getEntityIDs()) { 119 | BaseTag tag = (BaseTag) TagAPI.getTagTracker().getEntityTag(entityID); 120 | if (tag == null) 121 | continue; 122 | tag.destroyTagFor(event.getPlayer()); 123 | } 124 | } else if (packetType.equals(REL_ENTITY_MOVE) || packetType.equals(REL_ENTITY_MOVE_LOOK) || packetType.equals(ENTITY_TELEPORT) || packetType.equals(ENTITY_LOOK)) { 125 | if (!TagListener.this.listenForMovement) 126 | return; 127 | int entityID = event.getPacket().getIntegers().read(0); 128 | if (TagAPI.getTagTracker().isTagEntity(entityID)) 129 | return; 130 | 131 | BaseTag tag = (BaseTag) TagAPI.getTagTracker().getEntityTag(entityID); 132 | if (tag == null) 133 | return; 134 | 135 | if (packetType.equals(ENTITY_TELEPORT)) { 136 | if (!TagUtil.isViewer(event.getPlayer(), event.getPacket().getDoubles().read(0), event.getPacket().getDoubles().read(2))) { 137 | tag.destroyTagFor(event.getPlayer()); 138 | return; 139 | } 140 | } else if (!packetType.equals(ENTITY_LOOK)) { 141 | if (!TagUtil.isViewer(event.getPacket().getEntityModifier(event.getPlayer().getWorld()).read(0), event.getPlayer())) { 142 | tag.destroyTagFor(event.getPlayer()); 143 | return; 144 | } 145 | } 146 | 147 | if (packetType.equals(ENTITY_LOOK) || packetType.equals(REL_ENTITY_MOVE_LOOK)) { 148 | List packets = new ArrayList<>(); 149 | ((BaseTagEntity) tag.getBottomTagLine().getBottomEntity()).getMetaPackets(event.getPlayer(), packets, !event.getPlayer().isInvisible(), event.getPlayer().isSneaking()); 150 | packets.forEach(p -> p.sendPacket(event.getPlayer())); 151 | } 152 | 153 | } 154 | } 155 | 156 | }; 157 | /* 158 | This receiving adapter is to get rid of a single annoyance: Interaction with the fake entities. 159 | Without intercepting this packet, any player trying to interact with an entity with a tag would 160 | be clicking the tag instead. This adapter simply changes the interacted-with entity to the entity 161 | with the tag rather than the tag itself. If the fake tag-entity is not within the entities body, 162 | we will simply ignore it. 163 | */ 164 | this.receiveAdapter = new PacketAdapter(TagAPI.getPlugin(), ListenerPriority.LOWEST, PacketType.Play.Client.USE_ENTITY, PacketType.Play.Client.POSITION_LOOK, PacketType.Play.Client.LOOK, PacketType.Play.Client.POSITION) { 165 | 166 | @Override() 167 | public void onPacketReceiving(PacketEvent event) { 168 | PacketType packetType = event.getPacketType(); 169 | if (packetType.equals(PacketType.Play.Client.USE_ENTITY)) { 170 | int entityID = event.getPacket().getIntegers().read(0); 171 | if (!TagAPI.getTagTracker().isTagEntity(entityID)) 172 | return; 173 | 174 | TagLine tagLine = TagAPI.getTagTracker().getTagEntity(entityID).getTagLine(); 175 | if (!tagLine.interceptsTargetsBody()) 176 | return; 177 | 178 | Entity entity = tagLine.getTag().getTarget(); 179 | if (!(entity instanceof LivingEntity)) 180 | return; 181 | if (entity == event.getPlayer()) 182 | return; 183 | 184 | if (event.getPlayer().hasLineOfSight(entity)) 185 | event.getPacket().getIntegers().write(0, entity.getEntityId()); 186 | 187 | } else if (listenForMovement) { 188 | if (!packetType.equals(PacketType.Play.Client.POSITION_LOOK) || packetType.equals(PacketType.Play.Client.LOOK) || packetType.equals(PacketType.Play.Client.POSITION)) 189 | return; 190 | int entityID = event.getPlayer().getEntityId(); 191 | if (TagAPI.getTagTracker().isTagEntity(entityID)) 192 | return; 193 | BaseTag tag = (BaseTag) TagAPI.getTagTracker().getEntityTag(entityID); 194 | if (tag == null) 195 | return; 196 | tag.updateBottomStand(); 197 | } 198 | } 199 | }; 200 | ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager(); 201 | protocolManager.addPacketListener(this.sendAdapter); 202 | protocolManager.addPacketListener(this.receiveAdapter); 203 | } 204 | 205 | public void onDisable() { 206 | ProtocolLibrary.getProtocolManager().removePacketListener(this.sendAdapter); 207 | ProtocolLibrary.getProtocolManager().removePacketListener(this.receiveAdapter); 208 | 209 | Bukkit.getScheduler().cancelTask(this.taskID); 210 | } 211 | 212 | public void createTagForSpawnedEntity(int entityID, World world) { 213 | Entity entity = ProtocolLibrary.getProtocolManager().getEntityFromID(world, entityID); 214 | if (entity == null || !TagAPI.entityDefaultTags.containsKey(entity.getType()) || (entity.hasMetadata("had-default-tag"))) 215 | return; 216 | 217 | entity.setMetadata("had-default-tag", new FixedMetadataValue(TagAPI.getPlugin(), true)); 218 | TagAPI.entityDefaultTags.get(entity.getType()).apply(entity).giveTag(); 219 | initiatingEntitiesToIgnore.remove(Integer.valueOf(entityID)); 220 | } 221 | 222 | public void setupTask() { 223 | if (this.taskID != -1) 224 | Bukkit.getScheduler().cancelTask(this.taskID); 225 | this.taskID = Bukkit.getScheduler().scheduleSyncRepeatingTask(TagAPI.getPlugin(), () -> { 226 | for (Player player : Bukkit.getOnlinePlayers()) { 227 | if (!TagAPI.hasTag(player)) 228 | continue; 229 | ((BaseTag) TagAPI.getTag(player)).updateBottomStand(player); 230 | } 231 | }, 0L, 1L); 232 | } 233 | 234 | public void listenForMovement() { 235 | this.listenForMovement = true; 236 | this.setupTask(); 237 | } 238 | 239 | @EventHandler(priority = EventPriority.MONITOR) 240 | public void onDeath(EntityDeathEvent e) { 241 | this.onDespawn(e.getEntity()); 242 | } 243 | 244 | @EventHandler 245 | public void onUnload(ChunkUnloadEvent e) { 246 | for (Entity entity : e.getChunk().getEntities()) 247 | this.onDespawn(entity); 248 | } 249 | 250 | @EventHandler() 251 | public void onLeave(PlayerQuitEvent e) { 252 | TagAPI.getTagTracker().unregisterViewer(e.getPlayer()); 253 | e.getPlayer().removeMetadata("had-default-tag", TagAPI.getPlugin()); 254 | } 255 | 256 | @EventHandler(priority = EventPriority.MONITOR) 257 | public void onJoin(PlayerJoinEvent e) { 258 | BaseTag tag = (BaseTag) TagAPI.getTagTracker().getEntityTag(e.getPlayer().getEntityId()); 259 | if (tag == null) { 260 | if (!TagAPI.entityDefaultTags.containsKey(EntityType.PLAYER) || (e.getPlayer().hasMetadata("had-default-tag"))) 261 | return; 262 | 263 | e.getPlayer().setMetadata("had-default-tag", new FixedMetadataValue(TagAPI.getPlugin(), true)); 264 | TagAPI.entityDefaultTags.get(EntityType.PLAYER).apply(e.getPlayer()).giveTag(); 265 | } 266 | } 267 | 268 | private void onDespawn(Entity e) { 269 | if (TagAPI.getTagTracker().getEntityTag(e.getEntityId()) == null) 270 | return; 271 | ((BaseTag) TagAPI.getTagTracker().getEntityTag(e.getEntityId())).destroy(!(e instanceof Player)); 272 | } 273 | 274 | } 275 | -------------------------------------------------------------------------------- /src/main/java/com/lkeehl/tagapi/TagTracker.java: -------------------------------------------------------------------------------- 1 | package com.lkeehl.tagapi; 2 | 3 | import com.lkeehl.tagapi.api.Tag; 4 | import com.lkeehl.tagapi.api.TagEntity; 5 | import com.lkeehl.tagapi.tags.BaseTag; 6 | import com.lkeehl.tagapi.tags.BaseTagEntity; 7 | import com.lkeehl.tagapi.wrappers.Wrappers; 8 | import org.bukkit.entity.Player; 9 | 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | public class TagTracker { 14 | 15 | private final Map tagEntities = new HashMap<>(); 16 | private final Map entityTags = new HashMap<>(); 17 | 18 | public void trackEntity(BaseTagEntity entity) { 19 | this.tagEntities.put(entity.getEntityID(), entity); 20 | } 21 | 22 | public void stopTrackingEntity(BaseTagEntity entity) { 23 | this.tagEntities.remove(entity.getEntityID()); 24 | } 25 | 26 | public void setEntityTag(Integer entityID, BaseTag tag) { 27 | if (this.entityTags.containsKey(entityID) || tag == null) { 28 | Wrappers.DestroyPacket wrapper = Wrappers.DESTROY.get(); 29 | ((BaseTag) this.entityTags.get(entityID)).destroy(wrapper); 30 | wrapper.broadcastPacket(); 31 | } 32 | if (tag != null) 33 | this.entityTags.put(entityID, tag); 34 | else 35 | this.entityTags.remove(entityID); 36 | } 37 | 38 | public Tag getEntityTag(Integer entityID) { 39 | return this.entityTags.getOrDefault(entityID, null); 40 | } 41 | 42 | public TagEntity getTagEntity(int entityID) { 43 | return this.tagEntities.getOrDefault(entityID, null); 44 | } 45 | 46 | public boolean isTagEntity(int entityID) { 47 | return this.tagEntities.containsKey(entityID); 48 | } 49 | 50 | public void deleteTag(Tag tag) { 51 | entityTags.keySet().removeIf(uuid -> entityTags.get(uuid) == tag); 52 | } 53 | 54 | public void destroyAll() { 55 | Wrappers.DestroyPacket wrapper = Wrappers.DESTROY.get(); 56 | this.entityTags.values().forEach(tag -> ((BaseTag) tag).destroy(wrapper)); 57 | this.entityTags.clear(); 58 | this.tagEntities.clear(); 59 | 60 | wrapper.broadcastPacket(); 61 | } 62 | 63 | public void destroyAll(Player viewer) { 64 | Wrappers.DestroyPacket wrapper = Wrappers.DESTROY.get(); 65 | this.entityTags.values().forEach(tag -> ((BaseTag) tag).destroy(wrapper)); 66 | 67 | wrapper.sendPacket(viewer); 68 | } 69 | 70 | public void unregisterViewer(Player viewer) { 71 | for (Tag tag : this.entityTags.values()) 72 | ((BaseTag) tag).unregisterViewer(viewer); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/lkeehl/tagapi/api/Tag.java: -------------------------------------------------------------------------------- 1 | package com.lkeehl.tagapi.api; 2 | 3 | import com.lkeehl.tagapi.tags.BaseTag; 4 | import org.bukkit.entity.Entity; 5 | import org.bukkit.entity.Player; 6 | 7 | import java.util.List; 8 | 9 | public abstract class Tag { 10 | 11 | /** 12 | * Creates a new tag for a provided entity. 13 | * 14 | * @param target The entity that the tag should belong to. 15 | * @return A new Tag that can be edited. 16 | */ 17 | public static Tag create(Entity target) { 18 | if (target == null) 19 | return null; 20 | return BaseTag.create(target); 21 | } 22 | 23 | /** 24 | * Adds a new line to the tag with a given priority towards the top. 25 | * 26 | * @param importance The priority of the line towards the top. 27 | */ 28 | public abstract TagLine addTagLine(int importance); 29 | 30 | /** 31 | * Provides the entity that the tag is focused on. 32 | * 33 | * @return The entity that the tag is stuck to. 34 | */ 35 | public abstract Entity getTarget(); 36 | 37 | /** 38 | * Provides a collection of tag lines associated with this tag. 39 | * 40 | * @return This tags lines. 41 | */ 42 | public abstract List getTagLines(); 43 | 44 | /** 45 | * Returns the bottom-most TagLine. This is often a dummy line to adjust the 46 | * starting point of the developer-provided lines. 47 | * 48 | * @return The bottom-most TagLine. 49 | */ 50 | public abstract TagLine getBottomTagLine(); 51 | 52 | /** 53 | * Returns the top-most TagLine. 54 | * 55 | * @return The top-most TagLine. 56 | */ 57 | public abstract TagLine getTopTagLine(); 58 | 59 | /** 60 | * Updates the view of the tag for a provided player. 61 | * 62 | * @param viewer The player which the tag should update for. 63 | */ 64 | public abstract void updateTagFor(Player viewer); 65 | 66 | /** 67 | * Spawns and sets the target entities tag to this. 68 | */ 69 | public abstract void giveTag(); 70 | 71 | /** 72 | * Removes this tag from the target entity. 73 | */ 74 | public abstract void removeTag(); 75 | 76 | /** 77 | * Updates the tag for all players who can view it. 78 | */ 79 | public abstract void updateTag(); 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/lkeehl/tagapi/api/TagEntity.java: -------------------------------------------------------------------------------- 1 | package com.lkeehl.tagapi.api; 2 | 3 | import com.comphenix.protocol.PacketType; 4 | import com.comphenix.protocol.events.PacketContainer; 5 | import com.comphenix.protocol.wrappers.WrappedDataWatcher; 6 | 7 | import java.util.UUID; 8 | import java.util.function.BiConsumer; 9 | import java.util.function.Function; 10 | import java.util.function.Supplier; 11 | 12 | public interface TagEntity { 13 | 14 | /** 15 | * Returns the ID associated with this entity. 16 | * 17 | * @return The ID of the fake tag entity. 18 | **/ 19 | int getEntityID(); 20 | 21 | /** 22 | * Returns the UUID associated with this entity. 23 | * 24 | * @return The UUID for the fake tag entity. 25 | **/ 26 | UUID getEntityUUID(); 27 | 28 | /** 29 | * Returns the tag line that this entity belongs to. 30 | * 31 | * @return The TagLine for the fake tag entity. 32 | **/ 33 | TagLine getTagLine(); 34 | 35 | /** 36 | * Lines are created by mounting many invisible entities. This method here is for recursion purposes so that one can easily grab all the tag entities in a TagLine. 37 | * 38 | * @return The TagEntity that this is connected to, or null if none is present. 39 | **/ 40 | TagEntity getChild(); 41 | 42 | /** 43 | * Adds an extra packet that will be sent with the meta and spawn packets when this tag is created. 44 | **/ 45 | TagEntity injectPacket(Function packetContainer); 46 | 47 | /** 48 | * Allows the ability to add additional metadata properties to the tag's metadata packet. 49 | **/ 50 | TagEntity injectMetaData(BiConsumer metaDataConsumer); 51 | 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/lkeehl/tagapi/api/TagLine.java: -------------------------------------------------------------------------------- 1 | package com.lkeehl.tagapi.api; 2 | 3 | import com.lkeehl.tagapi.tags.BaseTag; 4 | import org.bukkit.entity.Player; 5 | 6 | import java.util.function.Function; 7 | 8 | public interface TagLine { 9 | 10 | /** 11 | * Provides a Function that determines what the line should say to a given player. 12 | * 13 | * @return This TagLine so that this class can be used in a builder format. 14 | */ 15 | TagLine setText(Function getName); 16 | 17 | @Deprecated TagLine setGetName(Function getName); 18 | 19 | /** 20 | * Provides a Function that determines whether this line should still be visible to a given player even if the line is null. 21 | * 22 | * @return This TagLine so that this class can be used in a builder format. 23 | */ 24 | TagLine setKeepSpaceWhenNull(Function keepSpaceWhenNull); 25 | 26 | /** 27 | * Allows you to control the visibility of specific tag lines for individual players. 28 | * 29 | * @param viewer The player whose visibility is being altered. 30 | * @param visible Whether the line should be visible for the provided player. 31 | */ 32 | void setVisibilityFor(Player viewer, boolean visible); 33 | 34 | /** 35 | * Returns the priority of this tag line towards the top. 36 | * 37 | * @return The priority of the line. 38 | */ 39 | int getImportance(); 40 | 41 | /** 42 | * Returns the Tag that this TagLine is associated with. 43 | * 44 | * @return The Tag this TagLine belongs to. 45 | */ 46 | Tag getTag(); 47 | 48 | /** 49 | * Returns the TagEntity that is the bottom of this TagLine. 50 | * 51 | * @return The TagEntity on the bottom of this TagLine. 52 | */ 53 | TagEntity getBottomEntity(); 54 | 55 | /** 56 | * Returns the TagEntity that is the top of this TagLine. This is always the armor stand. 57 | * 58 | * @return The TagEntity at the top of this TagLine. This is will represent an Armor Stand. 59 | */ 60 | TagEntity getTopEntity(); 61 | 62 | /** 63 | * Returns whether this tag should not be visible to a provided player 64 | * 65 | * @param viewer The player that is being checked 66 | * @return A boolean representing if this line is invisible to a player. 67 | */ 68 | boolean shouldHideFrom(Player viewer); 69 | 70 | /** 71 | * Returns whether the entity is within the body of the target entity. 72 | * 73 | * @return A boolean representing if this line is within the body of the target entity. 74 | */ 75 | boolean interceptsTargetsBody(); 76 | 77 | /** 78 | * Returns a line that will be visible to the provided player. 79 | * 80 | * @param viewer The player that is being checked. 81 | * @return The line that should show for the provided player. 82 | */ 83 | String getTextFor(Player viewer); 84 | 85 | @Deprecated String getNameFor(Player viewer); 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/com/lkeehl/tagapi/manager/TagManager.java: -------------------------------------------------------------------------------- 1 | package com.lkeehl.tagapi.manager; 2 | 3 | import com.lkeehl.tagapi.TagAPI; 4 | import org.bukkit.plugin.java.JavaPlugin; 5 | 6 | public class TagManager extends JavaPlugin { 7 | 8 | @Override() 9 | public void onEnable() { 10 | TagAPI.onEnable(this); 11 | TagAPI.listenForMovement(); 12 | } 13 | 14 | @Override() 15 | public void onDisable() { 16 | TagAPI.onDisable(); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/lkeehl/tagapi/tags/BaseTag.java: -------------------------------------------------------------------------------- 1 | package com.lkeehl.tagapi.tags; 2 | 3 | import com.lkeehl.tagapi.TagAPI; 4 | import com.lkeehl.tagapi.api.Tag; 5 | import com.lkeehl.tagapi.api.TagLine; 6 | import com.lkeehl.tagapi.wrappers.AbstractPacket; 7 | import com.lkeehl.tagapi.wrappers.Wrappers; 8 | import com.lkeehl.tagapi.util.TagUtil; 9 | import net.minecraft.network.protocol.game.PacketPlayInFlying; 10 | import org.bukkit.Bukkit; 11 | import org.bukkit.Location; 12 | import org.bukkit.entity.Entity; 13 | import org.bukkit.entity.LivingEntity; 14 | import org.bukkit.entity.Player; 15 | 16 | import java.util.*; 17 | 18 | public class BaseTag extends Tag { 19 | 20 | public static Tag create(Entity target) { 21 | return new BaseTag(target); 22 | } 23 | 24 | private final List tagLines = new ArrayList<>(); 25 | 26 | private final Map playerVisionCache = new HashMap<>(); 27 | 28 | private final Entity target; 29 | 30 | private BaseTag(Entity target) { 31 | this.target = target; 32 | 33 | this.addTagLine(new BaseTagLine(Integer.MIN_VALUE, this, true)); 34 | ((BaseTagLine) this.tagLines.get(0)).setInBody(); 35 | } 36 | 37 | private void addTagLine(BaseTagLine line) { 38 | this.tagLines.add(line); 39 | this.tagLines.sort(Comparator.comparingInt(TagLine::getImportance)); 40 | } 41 | 42 | public TagLine addTagLine(int importance) { 43 | BaseTagLine tagLine = new BaseTagLine(importance, this); 44 | this.addTagLine(tagLine); 45 | return tagLine; 46 | } 47 | 48 | public Entity getTarget() { 49 | return this.target; 50 | } 51 | 52 | public List getTagLines() { 53 | return this.tagLines; 54 | } 55 | 56 | public TagLine getBottomTagLine() { 57 | return this.tagLines.get(0); 58 | } 59 | 60 | public TagLine getTopTagLine() { 61 | return this.tagLines.get(this.tagLines.size() - 1); 62 | } 63 | 64 | private boolean isTargetVisible() { 65 | return !(this.target instanceof LivingEntity e) || !e.isInvisible(); 66 | } 67 | 68 | private boolean isTargetSneaking() { 69 | return this.target instanceof Player e && e.isSneaking(); 70 | } 71 | 72 | public void spawnTagFor(Player viewer) { 73 | this.spawnTagFor(viewer, this.isTargetVisible(), this.isTargetSneaking()); 74 | } 75 | 76 | public void spawnTagFor(Player viewer, boolean showName, boolean transparentName) { 77 | 78 | List spawnPackets = new ArrayList<>(); 79 | List mountPackets = new ArrayList<>(); 80 | Wrappers.DestroyPacket destroyWrapper = Wrappers.DESTROY.get(); 81 | BaseTagLine lastLine = null; 82 | int currentVision = this.playerVisionCache.getOrDefault(viewer.getEntityId(), 0); 83 | int vision = 0; 84 | for (int i = 0; i < (viewer == this.target ? 1 : this.tagLines.size()); i++) { 85 | BaseTagLine line = (BaseTagLine) this.tagLines.get(i); 86 | if (line.shouldHideFrom(viewer)) { 87 | if (((currentVision >> i) & 1) == 1) 88 | line.destroy(destroyWrapper); 89 | continue; 90 | } 91 | Location location = this.target.getLocation().clone(); 92 | spawnPackets.addAll(line.getSpawnPackets(viewer, location, ((currentVision >> i) & 1) == 0, showName, transparentName)); 93 | mountPackets.addAll(line.getMountPackets(viewer, lastLine)); 94 | lastLine = line; 95 | vision = vision | (1 << i); 96 | } 97 | Collections.reverse(mountPackets); 98 | this.playerVisionCache.put(viewer.getEntityId(), vision); 99 | 100 | spawnPackets.forEach(p -> p.sendPacket(viewer)); 101 | Bukkit.getScheduler().runTaskLater(TagAPI.getPlugin(), () -> mountPackets.forEach(p -> p.sendPacket(viewer)), 1); 102 | if (destroyWrapper.getCount() > 0) 103 | Bukkit.getScheduler().runTaskLater(TagAPI.getPlugin(), () -> destroyWrapper.sendPacket(viewer), 2); 104 | } 105 | 106 | public void updateBottomStand(Player viewer) { 107 | if (this.getBottomTagLine().shouldHideFrom(viewer)) 108 | return; 109 | Location location = this.target.getLocation().clone(); 110 | List spawnPackets = new ArrayList<>(); 111 | ((BaseTagEntity) this.getBottomTagLine().getBottomEntity()).getSpawnPackets(viewer, spawnPackets, location, false, this.isTargetVisible(), this.isTargetSneaking()); 112 | spawnPackets.forEach(p -> p.sendPacket(viewer)); 113 | } 114 | 115 | public void updateBottomStand() { 116 | TagUtil.getViewers(this.target).forEach(this::updateBottomStand); 117 | } 118 | 119 | public void destroyTagFor(Player viewer) { 120 | Wrappers.DestroyPacket wrapper = Wrappers.DESTROY.get(); 121 | this.destroy(wrapper); 122 | wrapper.sendPacket(viewer); 123 | this.playerVisionCache.remove(viewer.getEntityId()); 124 | } 125 | 126 | public void unregisterViewer(Player viewer) { 127 | this.playerVisionCache.remove(viewer.getEntityId()); 128 | } 129 | 130 | public void updateTagFor(Player viewer) { 131 | this.updateTagFor(viewer, this.isTargetVisible(), this.isTargetSneaking()); 132 | } 133 | 134 | public void updateTagFor(Player viewer, boolean showName, boolean transparentName) { 135 | int tempVision = 0; 136 | for (int i = 0; i < (viewer == this.target ? 1 : this.tagLines.size()); i++) { 137 | TagLine line = this.tagLines.get(i); 138 | if (line.shouldHideFrom(viewer)) 139 | continue; 140 | tempVision = tempVision | (1 << i); 141 | } 142 | if (tempVision != this.playerVisionCache.getOrDefault(viewer.getEntityId(), 0)) { 143 | this.spawnTagFor(viewer); 144 | } else { 145 | List metaPackets = new ArrayList<>(); 146 | for (TagLine line : tagLines) { 147 | if (line.shouldHideFrom(viewer)) 148 | continue; 149 | metaPackets.addAll(((BaseTagLine) line).getMetaPackets(viewer, showName, transparentName)); 150 | } 151 | 152 | metaPackets.forEach(p -> p.sendPacket(viewer)); 153 | } 154 | } 155 | 156 | public void giveTag() { 157 | BaseTag oldTag = (BaseTag) TagAPI.getTag(this.target); 158 | TagAPI.getTagTracker().setEntityTag(this.target.getEntityId(), this); 159 | if (oldTag != null) 160 | oldTag.getTagLines().stream().map(i -> (BaseTagLine) i).forEach(BaseTagLine::stopTrackingEntities); 161 | this.getTagLines().stream().map(i -> (BaseTagLine) i).forEach(BaseTagLine::trackEntities); 162 | TagUtil.getViewers(this.target).forEach(this::spawnTagFor); 163 | if (this.target instanceof Player) 164 | this.spawnTagFor((Player) this.target); 165 | } 166 | 167 | public void removeTag() { 168 | TagAPI.getTagTracker().setEntityTag(this.target.getEntityId(), null); 169 | Bukkit.getScheduler().runTaskLater(TagAPI.getPlugin(), () -> this.getTagLines().stream().map(i -> (BaseTagLine) i).forEach(BaseTagLine::stopTrackingEntities), 1L); 170 | } 171 | 172 | public void updateTag() { 173 | for (Player viewer : Bukkit.getOnlinePlayers()) 174 | this.updateTagFor(viewer); 175 | } 176 | 177 | public void destroy(boolean delete) { 178 | Wrappers.DestroyPacket wrapper = Wrappers.DESTROY.get(); 179 | this.destroy(wrapper); 180 | wrapper.broadcastPacket(); 181 | 182 | if (delete) 183 | TagAPI.getTagTracker().deleteTag(this); 184 | } 185 | 186 | public void destroy(Wrappers.DestroyPacket wrapper) { 187 | for (TagLine line : tagLines) 188 | ((BaseTagLine) line).destroy(wrapper); 189 | } 190 | 191 | 192 | } 193 | -------------------------------------------------------------------------------- /src/main/java/com/lkeehl/tagapi/tags/BaseTagEntity.java: -------------------------------------------------------------------------------- 1 | package com.lkeehl.tagapi.tags; 2 | 3 | import com.comphenix.protocol.events.PacketContainer; 4 | import com.comphenix.protocol.wrappers.WrappedDataWatcher; 5 | import com.lkeehl.tagapi.TagAPI; 6 | import com.lkeehl.tagapi.api.TagEntity; 7 | import com.lkeehl.tagapi.util.SetMap; 8 | import com.lkeehl.tagapi.util.TagUtil; 9 | import com.lkeehl.tagapi.util.VersionFile; 10 | import com.lkeehl.tagapi.util.WatcherType; 11 | import com.lkeehl.tagapi.wrappers.AbstractPacket; 12 | import com.lkeehl.tagapi.wrappers.AbstractSpawnPacket; 13 | import com.lkeehl.tagapi.wrappers.DevPacket; 14 | import com.lkeehl.tagapi.wrappers.Wrappers; 15 | import net.md_5.bungee.api.chat.TextComponent; 16 | import net.md_5.bungee.chat.ComponentSerializer; 17 | import net.minecraft.network.chat.IChatBaseComponent; 18 | import net.minecraft.world.entity.Entity; 19 | import org.bukkit.Location; 20 | import org.bukkit.entity.EntityType; 21 | import org.bukkit.entity.Player; 22 | 23 | import java.lang.reflect.Field; 24 | import java.util.*; 25 | import java.util.concurrent.atomic.AtomicInteger; 26 | import java.util.function.BiConsumer; 27 | import java.util.function.Function; 28 | import java.util.stream.Collectors; 29 | 30 | public class BaseTagEntity implements TagEntity { 31 | 32 | private final BaseTagLine tagLine; 33 | private final BaseTagEntity parent; 34 | private BaseTagEntity child; 35 | 36 | private final int entityID; 37 | private final UUID entityUUID; 38 | 39 | private final EntityType entityType; 40 | 41 | private final boolean nameEntity; 42 | 43 | private final List> injectedPackets = new ArrayList<>(); 44 | private final List> injectedMetaData = new ArrayList<>(); 45 | 46 | private static final SetMap entityWatchers = new SetMap<>(); 47 | 48 | private static VersionFile versionFile; 49 | 50 | private static AtomicInteger atomicEntityID; 51 | 52 | public static void init(VersionFile file) { 53 | entityWatchers.clear(); 54 | 55 | versionFile = file; 56 | List mobs = Arrays.asList(EntityType.ARMOR_STAND, EntityType.SILVERFISH, EntityType.SLIME, EntityType.TROPICAL_FISH, EntityType.TURTLE); 57 | 58 | for (EntityType mob : mobs) { 59 | entityWatchers.add(mob, new DataEntry(file.getDataWatcherIndex(mob, WatcherType.INVISIBLE_CROUCH), Byte.class, (byte) (1 << 5))); 60 | entityWatchers.add(mob, new DataEntry(file.getDataWatcherIndex(mob, WatcherType.CUSTOM_NAME), WrappedDataWatcher.Registry.getChatComponentSerializer(true), Optional.ofNullable(IChatBaseComponent.ChatSerializer.a("")))); 61 | entityWatchers.add(mob, new DataEntry(file.getDataWatcherIndex(mob, WatcherType.NAME_VISIBLE), Boolean.class, false)); 62 | entityWatchers.add(mob, new DataEntry(file.getDataWatcherIndex(mob, WatcherType.SILENT), Boolean.class, true)); 63 | } 64 | 65 | Function entry = i -> new DataEntry(file.getDataWatcherIndex(i, WatcherType.NO_AI), Byte.class, (byte) 1); 66 | entityWatchers.add(EntityType.SILVERFISH, entry.apply(EntityType.SILVERFISH)); 67 | entityWatchers.add(EntityType.SLIME, entry.apply(EntityType.SLIME)); 68 | entityWatchers.add(EntityType.TROPICAL_FISH, entry.apply(EntityType.TROPICAL_FISH)); 69 | entityWatchers.add(EntityType.TURTLE, entry.apply(EntityType.TURTLE)); 70 | 71 | entityWatchers.add(EntityType.ARMOR_STAND, new DataEntry(file.getDataWatcherIndex(EntityType.ARMOR_STAND, WatcherType.IS_SMALL_MARKER), Byte.class, (byte) 17)); 72 | entityWatchers.add(EntityType.SLIME, new DataEntry(file.getDataWatcherIndex(EntityType.SLIME, WatcherType.SIZE), Integer.class, -1)); 73 | entityWatchers.add(EntityType.TURTLE, new DataEntry(file.getDataWatcherIndex(EntityType.TURTLE, WatcherType.IS_BABY), Boolean.class, true)); 74 | 75 | Field[] fields = Entity.class.getDeclaredFields(); 76 | for (Field f : fields) { 77 | if (f.getType().equals(AtomicInteger.class)) { 78 | f.setAccessible(true); 79 | try { 80 | atomicEntityID = (AtomicInteger) f.get(null); 81 | } catch (IllegalAccessException e) { 82 | e.printStackTrace(); 83 | } 84 | } 85 | } 86 | 87 | } 88 | 89 | public BaseTagEntity(BaseTagLine tagLine, BaseTagEntity parent, EntityType entityType) { 90 | this(tagLine, parent, entityType, false); 91 | } 92 | 93 | public BaseTagEntity(BaseTagLine tagLine, BaseTagEntity parent, EntityType entityType, boolean nameEntity) { 94 | this.tagLine = tagLine; 95 | this.parent = parent; 96 | this.entityID = atomicEntityID.incrementAndGet(); 97 | this.entityUUID = UUID.randomUUID(); 98 | this.entityType = entityType; 99 | this.nameEntity = nameEntity; 100 | if (parent != null) 101 | parent.setChild(this); 102 | } 103 | 104 | public int getEntityID() { 105 | return this.entityID; 106 | } 107 | 108 | public UUID getEntityUUID() { 109 | return this.entityUUID; 110 | } 111 | 112 | public BaseTagLine getTagLine() { 113 | return this.tagLine; 114 | } 115 | 116 | public BaseTagEntity getChild() { 117 | return this.child; 118 | } 119 | 120 | protected void setChild(BaseTagEntity child) { 121 | this.child = child; 122 | } 123 | 124 | protected AbstractPacket getSpawnPacket(Location location) { 125 | AbstractSpawnPacket wrapper = this.entityType.isAlive() ? Wrappers.SPAWN_ENTITY_LIVING.get() : Wrappers.SPAWN_ENTITY.get(); 126 | wrapper.setID(this.entityID); 127 | wrapper.setUUID(this.entityUUID); 128 | wrapper.setLocation(location); 129 | wrapper.setType(this.entityType); 130 | wrapper.setVelocityX(0); 131 | wrapper.setVelocityY(0); 132 | wrapper.setVelocityZ(0); 133 | wrapper.setPitch(0.0F); 134 | wrapper.setYaw(0.0F); 135 | wrapper.setHeadYaw(0.0F); 136 | 137 | if (wrapper instanceof Wrappers.SpawnEntityPacket objectWrapper) 138 | objectWrapper.setObjectData(0); 139 | else { 140 | ((Wrappers.SpawnEntityLivingPacket) wrapper).setHeadYaw(0.0F); 141 | } 142 | 143 | return wrapper; 144 | } 145 | 146 | protected AbstractPacket getMetaPacket(Player viewer, boolean showName, boolean transparentName) { 147 | Wrappers.MetaDataPacket wrapper = Wrappers.METADATA.get(); 148 | wrapper.setEntityID(this.getEntityID()); 149 | 150 | WrappedDataWatcher watcher = new WrappedDataWatcher(); 151 | entityWatchers.get(this.entityType).forEach(entry -> entry.apply(watcher)); 152 | String name; 153 | if (transparentName) 154 | TagUtil.applyData(watcher, versionFile.getDataWatcherIndex(this.entityType, WatcherType.INVISIBLE_CROUCH), Byte.class, (byte) 34); 155 | if (this.nameEntity && (name = this.tagLine.getTextFor(viewer)) != null && showName) { 156 | TagUtil.applyData(watcher, versionFile.getDataWatcherIndex(this.entityType, WatcherType.NAME_VISIBLE), Boolean.class, true); // Name Visible 157 | TagUtil.applyData(watcher, versionFile.getDataWatcherIndex(this.entityType, WatcherType.CUSTOM_NAME), WrappedDataWatcher.Registry.getChatComponentSerializer(true), Optional.ofNullable(IChatBaseComponent.ChatSerializer.a(ComponentSerializer.toString(TextComponent.fromLegacyText(name))))); 158 | } 159 | 160 | this.injectedMetaData.forEach(i -> i.accept(this, watcher)); 161 | 162 | wrapper.setMetadata(watcher.getWatchableObjects()); 163 | return wrapper; 164 | } 165 | 166 | public void getSpawnPackets(Player viewer, List packets, Location location, boolean spawnNew, boolean showName, boolean transparentName) { 167 | if (this.child != null && viewer != this.tagLine.getTag().getTarget()) 168 | this.child.getSpawnPackets(viewer, packets, location, spawnNew, showName, transparentName); 169 | packets.add(this.getMetaPacket(viewer, showName, transparentName)); 170 | packets.addAll(this.injectedPackets.stream().map(i -> new DevPacket(i.apply(this))).collect(Collectors.toList())); 171 | if (spawnNew) 172 | packets.add(this.getSpawnPacket(location)); 173 | } 174 | 175 | public void getMountPackets(Player viewer, List packets, int defaultParentID) { 176 | if (this.child != null && viewer != this.tagLine.getTag().getTarget()) 177 | this.child.getMountPackets(viewer, packets, defaultParentID); 178 | 179 | packets.add(this.getMountPacket(defaultParentID)); 180 | } 181 | 182 | public void getMetaPackets(Player viewer, List packets, boolean showName, boolean transparentName) { 183 | if (this.child != null && viewer != this.tagLine.getTag().getTarget()) 184 | this.child.getMetaPackets(viewer, packets, showName, transparentName); 185 | 186 | packets.add(this.getMetaPacket(viewer, showName, transparentName)); 187 | packets.addAll(this.injectedPackets.stream().map(i -> new DevPacket(i.apply(this))).collect(Collectors.toList())); 188 | } 189 | 190 | public AbstractPacket getMountPacket(int defaultParentID) { 191 | Wrappers.MountPacket wrapper = Wrappers.MOUNT.get(); 192 | wrapper.setEntityID(this.parent == null ? defaultParentID : this.parent.getEntityID()); 193 | wrapper.setPassengerIds(new int[]{this.entityID}); 194 | return wrapper; 195 | } 196 | 197 | protected void trackLine() { 198 | if (this.child != null) 199 | this.child.trackLine(); 200 | 201 | TagAPI.getTagTracker().trackEntity(this); 202 | } 203 | 204 | protected void stopTrackingLine() { 205 | if (this.child != null) 206 | this.child.stopTrackingLine(); 207 | 208 | TagAPI.getTagTracker().stopTrackingEntity(this); 209 | } 210 | 211 | public void destroy(Wrappers.DestroyPacket wrapper) { 212 | if (this.child != null) 213 | this.child.destroy(wrapper); 214 | 215 | wrapper.addEntityID(this.entityID); 216 | } 217 | 218 | public TagEntity injectPacket(Function packetContainer) { 219 | this.injectedPackets.add(packetContainer); 220 | return this; 221 | } 222 | 223 | public TagEntity injectMetaData(BiConsumer metaDataConsumer) { 224 | this.injectedMetaData.add(metaDataConsumer); 225 | return this; 226 | } 227 | 228 | private static class DataEntry { 229 | 230 | private final Object value; 231 | 232 | protected WrappedDataWatcher.WrappedDataWatcherObject watcher; 233 | 234 | public DataEntry(int index, WrappedDataWatcher.Serializer serializer, Object value) { 235 | this.watcher = new WrappedDataWatcher.WrappedDataWatcherObject(index, serializer); 236 | this.value = value; 237 | } 238 | 239 | public DataEntry(int index, Class clazz, Object value) { 240 | this.watcher = new WrappedDataWatcher.WrappedDataWatcherObject(index, WrappedDataWatcher.Registry.get(clazz)); 241 | this.value = value; 242 | } 243 | 244 | public void apply(WrappedDataWatcher watcher) { 245 | watcher.setObject(this.watcher, this.value); 246 | } 247 | 248 | 249 | } 250 | 251 | } 252 | -------------------------------------------------------------------------------- /src/main/java/com/lkeehl/tagapi/tags/BaseTagLine.java: -------------------------------------------------------------------------------- 1 | package com.lkeehl.tagapi.tags; 2 | 3 | import com.lkeehl.tagapi.api.TagEntity; 4 | import com.lkeehl.tagapi.api.TagLine; 5 | import com.lkeehl.tagapi.wrappers.AbstractPacket; 6 | import com.lkeehl.tagapi.wrappers.Wrappers; 7 | import org.bukkit.Location; 8 | import org.bukkit.entity.EntityType; 9 | import org.bukkit.entity.Player; 10 | 11 | import java.util.*; 12 | import java.util.function.Function; 13 | 14 | public class BaseTagLine implements TagLine { 15 | 16 | private final BaseTag tag; 17 | 18 | private final BaseTagEntity bottomEntity; 19 | private final BaseTagEntity topEntity; 20 | 21 | private final Map visibilityMap = new HashMap<>(); 22 | 23 | private Function textFunction; 24 | private Function keepSpaceWhenNull; 25 | 26 | private final int importance; 27 | 28 | private boolean isInBody; 29 | 30 | protected BaseTagLine(int importance, BaseTag tag) { 31 | this(importance, tag, false); 32 | } 33 | 34 | protected BaseTagLine(int importance, BaseTag tag, boolean removeFish) { 35 | this.importance = importance; 36 | this.tag = tag; 37 | 38 | BaseTagEntity tempEntity; 39 | if (removeFish) { 40 | tempEntity = bottomEntity = new BaseTagEntity(this, null, EntityType.ARMOR_STAND,false); 41 | tempEntity = new BaseTagEntity(this, tempEntity, EntityType.TROPICAL_FISH); 42 | tempEntity = new BaseTagEntity(this, tempEntity, EntityType.SLIME); 43 | tempEntity = new BaseTagEntity(this, tempEntity, EntityType.TROPICAL_FISH); 44 | tempEntity = new BaseTagEntity(this, tempEntity, EntityType.TURTLE); 45 | } else { 46 | tempEntity = bottomEntity = new BaseTagEntity(this, null, EntityType.SILVERFISH); 47 | tempEntity = new BaseTagEntity(this, tempEntity, EntityType.SILVERFISH); 48 | } 49 | 50 | tempEntity = new BaseTagEntity(this, tempEntity, EntityType.SLIME); 51 | topEntity = new BaseTagEntity(this, tempEntity, EntityType.ARMOR_STAND, true); 52 | 53 | textFunction = (x) -> null; 54 | keepSpaceWhenNull = (x) -> true; 55 | } 56 | 57 | protected void setInBody() { 58 | this.isInBody = true; 59 | } 60 | 61 | protected void trackEntities() { 62 | this.bottomEntity.trackLine(); 63 | } 64 | 65 | protected void stopTrackingEntities() { 66 | this.bottomEntity.stopTrackingLine(); 67 | } 68 | 69 | public BaseTagLine setText(Function textFunction) { 70 | this.textFunction = textFunction; 71 | return this; 72 | } 73 | 74 | public BaseTagLine setGetName(Function getName) { 75 | return this.setText(getName); 76 | } 77 | 78 | public BaseTagLine setKeepSpaceWhenNull(Function keepSpaceWhenNull) { 79 | this.keepSpaceWhenNull = keepSpaceWhenNull; 80 | return this; 81 | } 82 | 83 | private boolean isVisibleTo(Player player) { 84 | return this.visibilityMap.getOrDefault(player.getUniqueId(), true); 85 | } 86 | 87 | public void setVisibilityFor(Player viewer, boolean visible) { 88 | if (!visible) 89 | this.visibilityMap.put(viewer.getUniqueId(), false); 90 | 91 | this.tag.destroyTagFor(viewer); 92 | this.tag.spawnTagFor(viewer); 93 | } 94 | 95 | public void destroy(Wrappers.DestroyPacket wrapper) { 96 | this.bottomEntity.destroy(wrapper); 97 | } 98 | 99 | public int getImportance() { 100 | return this.importance; 101 | } 102 | 103 | public BaseTag getTag() { 104 | return this.tag; 105 | } 106 | 107 | public BaseTagEntity getTopEntity() { 108 | return this.topEntity; 109 | } 110 | 111 | public BaseTagEntity getBottomEntity() { 112 | return this.bottomEntity; 113 | } 114 | 115 | public boolean shouldHideFrom(Player viewer) { 116 | String name = textFunction.apply(viewer); 117 | return !this.isVisibleTo(viewer) || (name == null && !keepSpaceWhenNull.apply(viewer)); 118 | } 119 | 120 | public boolean interceptsTargetsBody() { 121 | return this.isInBody; 122 | } 123 | 124 | public List getSpawnPackets(Player viewer, Location location, boolean spawnNew, boolean showName, boolean transparentName) { 125 | List packets = new ArrayList<>(); 126 | this.bottomEntity.getSpawnPackets(viewer, packets, location, spawnNew, showName, transparentName); 127 | Collections.reverse(packets); 128 | return packets; 129 | } 130 | 131 | public List getMetaPackets(Player viewer, boolean showName, boolean transparentName) { 132 | List packets = new ArrayList<>(); 133 | this.bottomEntity.getMetaPackets(viewer, packets, showName, transparentName); 134 | Collections.reverse(packets); 135 | return packets; 136 | } 137 | 138 | public List getMountPackets(Player viewer, BaseTagLine parent) { 139 | List packets = new ArrayList<>(); 140 | this.bottomEntity.getMountPackets(viewer, packets, parent == null ? this.tag.getTarget().getEntityId() : parent.getTopEntity().getEntityID()); 141 | Collections.reverse(packets); 142 | return packets; 143 | } 144 | 145 | public List getTagEntities() { 146 | List entities = new ArrayList<>(); 147 | TagEntity entity = this.bottomEntity; 148 | while (entity != null) { 149 | entities.add(entity); 150 | entity = entity.getChild(); 151 | } 152 | return entities; 153 | } 154 | 155 | public String getTextFor(Player viewer) { 156 | return this.textFunction.apply(viewer); 157 | } 158 | 159 | public String getNameFor(Player viewer) { 160 | return this.textFunction.apply(viewer); 161 | } 162 | 163 | 164 | } 165 | -------------------------------------------------------------------------------- /src/main/java/com/lkeehl/tagapi/util/SetMap.java: -------------------------------------------------------------------------------- 1 | package com.lkeehl.tagapi.util; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | public class SetMap { 9 | 10 | private final Map> values = new HashMap<>(); 11 | 12 | public void put(K key, List value) { 13 | this.values.put(key, value); 14 | } 15 | 16 | public void add(K key, E value) { 17 | if (!this.values.containsKey(key)) 18 | this.values.put(key, new ArrayList<>()); 19 | if (value == null) 20 | return; 21 | this.values.get(key).add(value); 22 | } 23 | 24 | @SuppressWarnings("unchecked") 25 | public void add(K key, E... values) { 26 | if (!this.values.containsKey(key)) 27 | this.values.put(key, new ArrayList<>()); 28 | for (E value : values) 29 | this.add(key, value); 30 | } 31 | 32 | public void remove(K key, E value) { 33 | if (!this.values.containsKey(key)) 34 | return; 35 | if (!this.values.get(key).contains(value)) 36 | return; 37 | this.values.get(key).remove(value); 38 | } 39 | 40 | public void clear(K key) { 41 | this.values.remove(key); 42 | } 43 | 44 | public void clear() { 45 | this.values.clear(); 46 | } 47 | 48 | public void addAll(K key, List values) { 49 | if (!this.values.containsKey(key)) 50 | this.values.put(key, new ArrayList<>()); 51 | this.values.get(key).addAll(values); 52 | } 53 | 54 | public void addBlank(K key){ 55 | if (!this.values.containsKey(key)) 56 | this.values.put(key, new ArrayList<>()); 57 | } 58 | 59 | public void removeAll(K key, List values) { 60 | if (!this.values.containsKey(key)) 61 | return; 62 | this.values.get(key).removeAll(values); 63 | } 64 | 65 | public List get(K key) { 66 | if (!this.values.containsKey(key)) 67 | return new ArrayList<>(); 68 | return this.values.get(key); 69 | } 70 | 71 | public List getKey(K key) { 72 | if (!this.values.containsKey(key)) 73 | return new ArrayList<>(); 74 | return this.values.get(key); 75 | } 76 | 77 | public List get(int index) { 78 | if (this.values.isEmpty()) 79 | return new ArrayList<>(); 80 | return this.values.get(new ArrayList<>(this.values.keySet()).get(index)); 81 | } 82 | 83 | public List getIndex(int index) { 84 | if (this.values.isEmpty()) 85 | return new ArrayList<>(); 86 | return this.values.get(new ArrayList<>(this.values.keySet()).get(index)); 87 | } 88 | 89 | public int size() { 90 | return this.values.size(); 91 | } 92 | 93 | public boolean containsKey(K key) { 94 | return this.values.containsKey(key); 95 | } 96 | 97 | public List keySet() { 98 | if (this.values.isEmpty()) 99 | return new ArrayList<>(); 100 | return new ArrayList<>(this.values.keySet()); 101 | } 102 | 103 | public List values() { 104 | if (this.values.isEmpty()) 105 | return new ArrayList<>(); 106 | List values = new ArrayList<>(); 107 | for (K key : this.values.keySet()) 108 | values.addAll(this.values.get(key)); 109 | return values; 110 | } 111 | 112 | } -------------------------------------------------------------------------------- /src/main/java/com/lkeehl/tagapi/util/TagUtil.java: -------------------------------------------------------------------------------- 1 | package com.lkeehl.tagapi.util; 2 | 3 | import com.comphenix.protocol.wrappers.WrappedDataWatcher; 4 | import org.bukkit.Location; 5 | import org.bukkit.entity.Entity; 6 | import org.bukkit.entity.Player; 7 | 8 | import java.util.stream.Stream; 9 | 10 | public class TagUtil { 11 | 12 | public static Stream getViewers(Entity entity) { 13 | return entity.getWorld().getPlayers().stream().filter(p -> isViewer(entity, p)); 14 | } 15 | 16 | public static boolean isViewer(Entity entity, Player player) { 17 | return isViewer(player, entity.getLocation().getX(), entity.getLocation().getZ()); 18 | } 19 | 20 | public static boolean isViewer(Player player, double x, double z) { 21 | return Math.abs(player.getLocation().getX() - x) <= 48 && Math.abs(player.getLocation().getZ() - z) <= 48; 22 | } 23 | 24 | public static void applyData(WrappedDataWatcher watcher, int index, Class clazz, H value) { 25 | WrappedDataWatcher.WrappedDataWatcherObject object = new WrappedDataWatcher.WrappedDataWatcherObject(index, WrappedDataWatcher.Registry.get(clazz)); 26 | watcher.setObject(object, value); 27 | } 28 | 29 | public static void applyData(WrappedDataWatcher watcher, int index, WrappedDataWatcher.Serializer serializer, H value) { 30 | WrappedDataWatcher.WrappedDataWatcherObject object = new WrappedDataWatcher.WrappedDataWatcherObject(index, serializer); 31 | watcher.setObject(object, value); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/lkeehl/tagapi/util/VersionFile.java: -------------------------------------------------------------------------------- 1 | package com.lkeehl.tagapi.util; 2 | 3 | import com.lkeehl.tagapi.TagAPI; 4 | import org.bukkit.Bukkit; 5 | import org.bukkit.entity.EntityType; 6 | 7 | import java.io.DataInputStream; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.util.*; 11 | import java.util.TreeSet; 12 | import java.util.function.Function; 13 | import java.util.stream.Collectors; 14 | import java.util.zip.GZIPInputStream; 15 | 16 | /* 17 | The system I use for reading NBT is heavily influenced by code from Querz. 18 | This version has been heavily modified to read only. On top of that, it only 19 | reads integers and compounds as that is all I personally needed. Check out their 20 | full resource here: https://github.com/Querz/NBT 21 | 22 | Yes, MC has a built-in NBT system; however, the package and methods change with NMS 23 | every-so-often like from 1.17 to 1.18. I would rather not rely on NMS for an API. 24 | */ 25 | 26 | @SuppressWarnings("unchecked") 27 | public class VersionFile { 28 | 29 | private final Tag>> versionTag; 30 | 31 | public VersionFile() { 32 | InputStream fileStream = getClass().getResourceAsStream("/versions.cult"); 33 | assert fileStream != null; 34 | 35 | Tag>> mainTag = null; 36 | Throwable var3 = null; 37 | try { 38 | DataInputStream stream = new DataInputStream(new GZIPInputStream(fileStream)); 39 | 40 | byte id = stream.readByte(); 41 | stream.readUTF(); 42 | mainTag = (Tag>>) readTag(id, 512, stream); 43 | } catch (Exception var13) { 44 | var3 = var13; 45 | } finally { 46 | if (var3 != null) { 47 | try { 48 | fileStream.close(); 49 | } catch (Throwable var12) { 50 | var3.addSuppressed(var12); 51 | } 52 | } else { 53 | try { 54 | fileStream.close(); 55 | } catch (IOException e) { 56 | e.printStackTrace(); 57 | } 58 | } 59 | } 60 | if (var3 != null) 61 | var3.printStackTrace(); 62 | if (mainTag == null) 63 | mainTag = new Tag<>(new HashMap<>(8)); 64 | 65 | Function parse = (version) -> { 66 | String[] split = version.split("\\."); 67 | return (short) ((Integer.parseInt(split[0]) << 10) | ((Integer.parseInt(split[1]) << 5) | (split.length == 3 ? Integer.parseInt(split[2]) : 0))); 68 | }; 69 | 70 | short semantic = parse.apply(Bukkit.getBukkitVersion().substring(0, Bukkit.getBukkitVersion().indexOf("-"))); 71 | Comparator comp = (x, y) -> (x > semantic || y > semantic) ? ((x > semantic && y > semantic) ? Short.compare(x, y) : (x > semantic ? 1 : -1)) : -Short.compare(x, y); 72 | TreeSet versions = new TreeSet<>(comp); 73 | versions.addAll(mainTag.getValue().keySet().stream().map(parse).collect(Collectors.toList())); 74 | Tag>> tempTag = (Tag>>) mainTag.getValue().get(String.format("%s.%s.%s", ((versions.first() >> 10) & 31), ((versions.first() >> 5) & 31), (versions.first() & 31))); 75 | if (tempTag.getValue().isEmpty()) { 76 | versions.remove(versions.first()); 77 | while (!versions.isEmpty()) { 78 | if ((tempTag = (Tag>>) mainTag.getValue().get(String.format("%s.%s.%s", ((versions.first() >> 10) & 31), ((versions.first() >> 5) & 31), (versions.first() & 31)))).getValue().isEmpty()) 79 | versions.remove(versions.first()); 80 | else 81 | break; 82 | } 83 | if (!versions.isEmpty()) 84 | TagAPI.getPlugin().getLogger().info("TagAPI is using entity metadata from " + String.format("%s.%s.%s", ((versions.first() >> 10) & 31), ((versions.first() >> 5) & 31), (versions.first() & 31)) + " for this version of Minecraft."); 85 | else { 86 | tempTag = new Tag<>(new HashMap<>(8)); 87 | TagAPI.getPlugin().getLogger().warning("TagAPI is was unable to find a suitable entity metadata set for this Minecraft version! Tags entities may not appear properly! An update might be available for the \"" + TagAPI.getPlugin().getName() + "\" plugin."); 88 | } 89 | } else { 90 | if (semantic != versions.first()) 91 | TagAPI.getPlugin().getLogger().warning("TagAPI is using entity metadata from " + String.format("%s.%s.%s", ((versions.first() >> 10) & 31), ((versions.first() >> 5) & 31), (versions.first() & 31)) + " for this version of Minecraft. If issues are present, an update might be required for the \"" + TagAPI.getPlugin().getName() + "\" plugin."); 92 | } 93 | this.versionTag = tempTag; 94 | } 95 | 96 | private Tag readTag(byte type, int maxDepth, DataInputStream stream) throws IOException { 97 | if (type == 3) 98 | return new Tag<>(stream.readInt()); 99 | else if (type == 10) { 100 | Tag>> comp = new Tag<>(new HashMap<>(8)); 101 | for (int id = stream.readByte() & 0xFF; id != 0; id = stream.readByte() & 0xFF) { 102 | String key = stream.readUTF(); 103 | if (maxDepth < 0) 104 | throw new IllegalArgumentException("negative maximum depth is not allowed"); 105 | else if (maxDepth == 0) 106 | throw new RuntimeException("reached maximum depth of NBT structure"); 107 | Tag element = this.readTag((byte) id, --maxDepth, stream); 108 | comp.getValue().put(Objects.requireNonNull(key), Objects.requireNonNull(element)); 109 | } 110 | return comp; 111 | } else 112 | throw new IOException("invalid tag id \"" + type + "\""); 113 | } 114 | 115 | public int getDataWatcherIndex(EntityType entity, WatcherType watcher) { 116 | if (!this.versionTag.getValue().containsKey(entity.toString())) 117 | return -1; 118 | Tag>> entityCompound = (Tag>>) this.versionTag.getValue().get(entity.toString()); 119 | if (!entityCompound.getValue().containsKey(watcher.toString())) 120 | return -1; 121 | return (int) entityCompound.getValue().get(watcher.toString()).getValue(); 122 | } 123 | 124 | public int getEntityID(EntityType entity) { 125 | if (!this.versionTag.getValue().containsKey(entity.toString())) 126 | return -1; 127 | Tag>> entityCompound = (Tag>>) this.versionTag.getValue().get(entity.toString()); 128 | if (!entityCompound.getValue().containsKey("ENTITY-ID")) 129 | return -1; 130 | return (int) entityCompound.getValue().get("ENTITY-ID").getValue(); 131 | } 132 | 133 | public record Tag(T value) { 134 | 135 | public Tag(T value) { 136 | this.value = Objects.requireNonNull(value); 137 | } 138 | 139 | private T getValue() { 140 | return value; 141 | } 142 | 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /src/main/java/com/lkeehl/tagapi/util/WatcherType.java: -------------------------------------------------------------------------------- 1 | package com.lkeehl.tagapi.util; 2 | 3 | public enum WatcherType { 4 | 5 | INVISIBLE_CROUCH, CUSTOM_NAME, NAME_VISIBLE, SILENT, NO_AI, IS_SMALL_MARKER, SIZE, IS_BABY 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/lkeehl/tagapi/wrappers/AbstractPacket.java: -------------------------------------------------------------------------------- 1 | package com.lkeehl.tagapi.wrappers; 2 | 3 | import com.comphenix.protocol.ProtocolLibrary; 4 | import com.comphenix.protocol.events.PacketContainer; 5 | import org.bukkit.entity.Player; 6 | 7 | import java.lang.reflect.InvocationTargetException; 8 | 9 | public abstract class AbstractPacket { 10 | // The packet we will be modifying 11 | protected PacketContainer handle; 12 | 13 | /** 14 | * Constructs a new strongly typed wrapper for the given packet. 15 | * 16 | * @param handle - handle to the raw packet data. 17 | */ 18 | protected AbstractPacket(PacketContainer handle) { 19 | // Make sure we're given a valid packet 20 | if (handle == null) 21 | throw new IllegalArgumentException("Packet handle cannot be NULL."); 22 | 23 | this.handle = handle; 24 | } 25 | 26 | /** 27 | * Retrieve a handle to the raw packet data. 28 | * 29 | * @return Raw packet data. 30 | */ 31 | public PacketContainer getHandle() { 32 | return handle; 33 | } 34 | 35 | /** 36 | * Send the current packet to the given receiver. 37 | * 38 | * @param receiver - the receiver. 39 | * @throws RuntimeException If the packet cannot be sent. 40 | */ 41 | public void sendPacket(Player receiver) { 42 | try { 43 | ProtocolLibrary.getProtocolManager().sendServerPacket(receiver, getHandle()); 44 | } catch (InvocationTargetException e) { 45 | throw new RuntimeException("Cannot send packet.", e); 46 | } 47 | } 48 | 49 | /** 50 | * Send the current packet to all online players. 51 | */ 52 | public void broadcastPacket() { 53 | ProtocolLibrary.getProtocolManager().broadcastServerPacket(getHandle()); 54 | } 55 | 56 | /** 57 | * Simulate receiving the current packet from the given sender. 58 | * 59 | * @param sender - the sender. 60 | * @throws RuntimeException if the packet cannot be received. 61 | */ 62 | public void receivePacket(Player sender) { 63 | try { 64 | ProtocolLibrary.getProtocolManager().recieveClientPacket(sender, getHandle()); 65 | } catch (Exception e) { 66 | throw new RuntimeException("Cannot receive packet.", e); 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/main/java/com/lkeehl/tagapi/wrappers/AbstractSpawnPacket.java: -------------------------------------------------------------------------------- 1 | package com.lkeehl.tagapi.wrappers; 2 | 3 | import com.comphenix.protocol.PacketType; 4 | import com.comphenix.protocol.events.PacketContainer; 5 | import org.bukkit.Location; 6 | import org.bukkit.entity.EntityType; 7 | 8 | import java.util.UUID; 9 | 10 | public abstract class AbstractSpawnPacket extends AbstractPacket{ 11 | 12 | protected AbstractSpawnPacket(PacketType type) { 13 | super(new PacketContainer(type)); 14 | handle.getModifier().writeDefaults(); 15 | } 16 | 17 | protected AbstractSpawnPacket(PacketContainer container) { 18 | super(container); 19 | } 20 | 21 | public abstract void setID(int entityID); 22 | 23 | public abstract void setUUID(UUID uuid); 24 | 25 | public abstract void setType(EntityType type); 26 | 27 | public abstract void setLocation(Location location); 28 | 29 | public abstract void setYaw(float value); 30 | 31 | public abstract void setPitch(float value); 32 | 33 | public abstract void setHeadYaw(float value); 34 | 35 | public abstract void setVelocityX(int value); 36 | 37 | public abstract void setVelocityY(int value); 38 | 39 | public abstract void setVelocityZ(int value); 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/lkeehl/tagapi/wrappers/DevPacket.java: -------------------------------------------------------------------------------- 1 | package com.lkeehl.tagapi.wrappers; 2 | 3 | import com.comphenix.protocol.PacketType; 4 | import com.comphenix.protocol.events.PacketContainer; 5 | 6 | public class DevPacket extends AbstractPacket { 7 | /** 8 | * Constructs a new strongly typed wrapper for the given packet. 9 | * 10 | * @param handle - handle to the raw packet data. 11 | */ 12 | public DevPacket(PacketContainer handle) { 13 | super(handle); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/lkeehl/tagapi/wrappers/Wrappers.java: -------------------------------------------------------------------------------- 1 | package com.lkeehl.tagapi.wrappers; 2 | 3 | import com.comphenix.protocol.PacketType; 4 | import com.comphenix.protocol.events.PacketContainer; 5 | import com.comphenix.protocol.wrappers.WrappedWatchableObject; 6 | 7 | import java.util.List; 8 | import java.util.function.Function; 9 | import java.util.function.Supplier; 10 | 11 | public class Wrappers { 12 | 13 | public static Supplier SPAWN_ENTITY; 14 | public static Supplier SPAWN_ENTITY_LIVING; 15 | public static Supplier MOUNT; 16 | public static Supplier METADATA; 17 | public static Function METADATA_W_CONTAINER; 18 | public static Supplier DESTROY; 19 | public static Function DESTROY_W_CONTAINER; 20 | 21 | public static PacketType[] packetTypes; 22 | 23 | public abstract static class SpawnEntityPacket extends AbstractSpawnPacket { 24 | 25 | protected SpawnEntityPacket() { 26 | super(PacketType.Play.Server.SPAWN_ENTITY); 27 | handle.getModifier().writeDefaults(); 28 | } 29 | 30 | public abstract void setX(double value); 31 | 32 | public abstract void setY(double value); 33 | 34 | public abstract void setZ(double value); 35 | 36 | public abstract void setObjectData(int value); 37 | 38 | public abstract void setPitch(float value); 39 | 40 | public abstract void setYaw(float value); 41 | 42 | public abstract void setHeadYaw(float value); 43 | 44 | } 45 | 46 | public abstract static class SpawnEntityLivingPacket extends AbstractSpawnPacket { 47 | 48 | protected SpawnEntityLivingPacket() { 49 | super(PacketType.Play.Server.SPAWN_ENTITY_LIVING); 50 | handle.getModifier().writeDefaults(); 51 | } 52 | 53 | } 54 | 55 | public abstract static class MountPacket extends AbstractPacket { 56 | 57 | protected MountPacket() { 58 | super(new PacketContainer( PacketType.Play.Server.MOUNT)); 59 | handle.getModifier().writeDefaults(); 60 | } 61 | 62 | public abstract void setEntityID(int value); 63 | 64 | public abstract int[] getPassengerIds(); 65 | 66 | public abstract void setPassengerIds(int[] value); 67 | 68 | 69 | } 70 | 71 | public abstract static class MetaDataPacket extends AbstractPacket { 72 | 73 | protected MetaDataPacket() { 74 | super(new PacketContainer( PacketType.Play.Server.ENTITY_METADATA)); 75 | handle.getModifier().writeDefaults(); 76 | } 77 | 78 | public MetaDataPacket(PacketContainer packet) { 79 | super(packet); 80 | } 81 | 82 | public abstract void setEntityID(int value); 83 | 84 | public abstract List getMetadata(); 85 | 86 | public abstract void setMetadata(List value); 87 | 88 | 89 | } 90 | 91 | public abstract static class DestroyPacket extends AbstractPacket { 92 | 93 | protected DestroyPacket() { 94 | super(new PacketContainer( PacketType.Play.Server.ENTITY_DESTROY)); 95 | handle.getModifier().writeDefaults(); 96 | } 97 | 98 | public DestroyPacket(PacketContainer packet) { 99 | super(packet); 100 | } 101 | 102 | public abstract int getCount(); 103 | 104 | public abstract List getEntityIDs(); 105 | 106 | public abstract void addEntityID(int id); 107 | 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/com/lkeehl/tagapi/wrappers/versions/Wrapper1_17_1.java: -------------------------------------------------------------------------------- 1 | package com.lkeehl.tagapi.wrappers.versions; 2 | 3 | import com.comphenix.protocol.PacketType; 4 | import com.comphenix.protocol.events.PacketContainer; 5 | import com.comphenix.protocol.wrappers.WrappedWatchableObject; 6 | import com.lkeehl.tagapi.util.VersionFile; 7 | import com.lkeehl.tagapi.wrappers.Wrappers; 8 | import org.bukkit.Location; 9 | import org.bukkit.entity.EntityType; 10 | 11 | import java.util.List; 12 | import java.util.UUID; 13 | 14 | import static com.comphenix.protocol.PacketType.Play.Server.*; 15 | import static com.comphenix.protocol.PacketType.Play.Server.ENTITY_TELEPORT; 16 | 17 | public class Wrapper1_17_1 { 18 | 19 | private static VersionFile versionFile; 20 | 21 | public static void init(VersionFile versionFile) { 22 | Wrapper1_17_1.versionFile = versionFile; 23 | 24 | Wrappers.DESTROY_W_CONTAINER = DestroyWrapper::new; 25 | Wrappers.DESTROY = DestroyWrapper::new; 26 | Wrappers.METADATA_W_CONTAINER = MetaDataWrapper::new; 27 | Wrappers.METADATA = MetaDataWrapper::new; 28 | Wrappers.MOUNT = MountWrapper::new; 29 | Wrappers.SPAWN_ENTITY = SpawnEntityWrapper::new; 30 | Wrappers.SPAWN_ENTITY_LIVING = SpawnEntityLivingWrapper::new; 31 | 32 | Wrappers.packetTypes = new PacketType[]{SPAWN_ENTITY, SPAWN_ENTITY_LIVING, ENTITY_DESTROY, NAMED_ENTITY_SPAWN, ENTITY_METADATA, PLAYER_INFO, REL_ENTITY_MOVE, REL_ENTITY_MOVE_LOOK, ENTITY_LOOK, ENTITY_TELEPORT}; 33 | } 34 | 35 | public static class DestroyWrapper extends Wrappers.DestroyPacket { 36 | 37 | public DestroyWrapper() { 38 | super(); 39 | } 40 | 41 | public DestroyWrapper(PacketContainer packet) { 42 | super(packet); 43 | } 44 | 45 | @Override 46 | public int getCount() { 47 | return handle.getIntLists().read(0).size(); 48 | } 49 | 50 | @Override 51 | public List getEntityIDs() { 52 | return handle.getIntLists().read(0); 53 | } 54 | 55 | @Override 56 | public void addEntityID(int id) { 57 | List ids = this.getEntityIDs(); 58 | ids.add(id); 59 | handle.getIntLists().write(0, ids); 60 | } 61 | } 62 | 63 | public static class MetaDataWrapper extends Wrappers.MetaDataPacket { 64 | 65 | public MetaDataWrapper() { 66 | super(); 67 | } 68 | 69 | public MetaDataWrapper(PacketContainer packet) { 70 | super(packet); 71 | } 72 | 73 | @Override 74 | public void setEntityID(int value) { 75 | handle.getIntegers().write(0, value); 76 | } 77 | 78 | @Override 79 | public List getMetadata() { 80 | return handle.getWatchableCollectionModifier().read(0); 81 | } 82 | 83 | @Override 84 | public void setMetadata(List value) { 85 | handle.getWatchableCollectionModifier().write(0, value); 86 | } 87 | } 88 | 89 | public static class MountWrapper extends Wrappers.MountPacket { 90 | 91 | @Override 92 | public void setEntityID(int value) { 93 | handle.getIntegers().write(0, value); 94 | } 95 | 96 | @Override 97 | public int[] getPassengerIds() { 98 | return handle.getIntegerArrays().read(0); 99 | } 100 | 101 | @Override 102 | public void setPassengerIds(int[] value) { 103 | handle.getIntegerArrays().write(0, value); 104 | } 105 | } 106 | 107 | public static class SpawnEntityWrapper extends Wrappers.SpawnEntityPacket { 108 | 109 | @Override 110 | public void setID(int entityID) { 111 | handle.getIntegers().write(0, entityID); 112 | } 113 | 114 | @Override 115 | public void setUUID(UUID uuid) { 116 | handle.getUUIDs().write(0, uuid); 117 | } 118 | 119 | @Override 120 | public void setType(EntityType type) { 121 | handle.getEntityTypeModifier().write(0, type); 122 | } 123 | 124 | @Override 125 | public void setLocation(Location location) { 126 | this.setX(location.getX()); 127 | this.setY(location.getY()); 128 | this.setZ(location.getZ()); 129 | } 130 | 131 | @Override 132 | public void setYaw(float value) { 133 | handle.getIntegers().write(5, (int) (value * 256.0F / 360.0F)); 134 | } 135 | 136 | @Override 137 | public void setHeadYaw(float value) {} 138 | 139 | @Override 140 | public void setPitch(float value) { 141 | handle.getIntegers().write(4, (int) (value * 256.0F / 360.0F)); 142 | } 143 | 144 | @Override 145 | public void setVelocityX(int value) { 146 | handle.getIntegers().write(1, (int) (value * 8000.0D)); 147 | } 148 | 149 | @Override 150 | public void setVelocityY(int value) { 151 | handle.getIntegers().write(2, (int) (value * 8000.0D)); 152 | } 153 | 154 | @Override 155 | public void setVelocityZ(int value) { 156 | handle.getIntegers().write(3, (int) (value * 8000.0D)); 157 | } 158 | 159 | @Override 160 | public void setX(double value) { 161 | handle.getDoubles().write(0, value); 162 | } 163 | 164 | @Override 165 | public void setY(double value) { 166 | handle.getDoubles().write(1, value); 167 | } 168 | 169 | @Override 170 | public void setZ(double value) { 171 | handle.getDoubles().write(2, value); 172 | } 173 | 174 | @Override 175 | public void setObjectData(int value) { 176 | handle.getIntegers().write(6, value); 177 | } 178 | } 179 | 180 | public static class SpawnEntityLivingWrapper extends Wrappers.SpawnEntityLivingPacket { 181 | 182 | @Override 183 | public void setID(int entityID) { 184 | this.getHandle().getIntegers().write(0, entityID); 185 | } 186 | 187 | @Override 188 | public void setUUID(UUID uuid) { 189 | this.getHandle().getUUIDs().write(0, uuid); 190 | } 191 | 192 | @Override 193 | public void setType(EntityType type) { 194 | handle.getIntegers().write(1, versionFile.getEntityID(type)); 195 | } 196 | 197 | @Override 198 | public void setLocation(Location location) { 199 | handle.getDoubles().write(0, location.getX()); 200 | handle.getDoubles().write(1, location.getY()); 201 | handle.getDoubles().write(2, location.getZ()); 202 | } 203 | 204 | @Override 205 | public void setYaw(float value) { 206 | handle.getBytes().write(0, (byte) (value * 256.0F / 360.0F)); 207 | } 208 | 209 | @Override 210 | public void setPitch(float value) { 211 | handle.getBytes().write(1, (byte) (value * 256.0F / 360.0F)); 212 | } 213 | 214 | @Override 215 | public void setVelocityX(int value) { 216 | handle.getIntegers().write(2, value); 217 | } 218 | 219 | @Override 220 | public void setVelocityY(int value) { 221 | handle.getIntegers().write(3, value); 222 | } 223 | 224 | @Override 225 | public void setVelocityZ(int value) { 226 | handle.getIntegers().write(4, value); 227 | } 228 | 229 | @Override 230 | public void setHeadYaw(float value) { 231 | handle.getBytes().write(2, (byte) (value * 256.0F / 360.0F)); 232 | } 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /src/main/java/com/lkeehl/tagapi/wrappers/versions/Wrapper1_18_1.java: -------------------------------------------------------------------------------- 1 | package com.lkeehl.tagapi.wrappers.versions; 2 | 3 | import com.comphenix.protocol.PacketType; 4 | import com.comphenix.protocol.events.PacketContainer; 5 | import com.comphenix.protocol.wrappers.WrappedWatchableObject; 6 | import com.lkeehl.tagapi.util.VersionFile; 7 | import com.lkeehl.tagapi.wrappers.Wrappers; 8 | import org.bukkit.Location; 9 | import org.bukkit.entity.EntityType; 10 | 11 | import java.util.List; 12 | import java.util.UUID; 13 | 14 | import static com.comphenix.protocol.PacketType.Play.Server.*; 15 | import static com.comphenix.protocol.PacketType.Play.Server.ENTITY_TELEPORT; 16 | 17 | public class Wrapper1_18_1 { 18 | 19 | private static VersionFile versionFile; 20 | 21 | public static void init(VersionFile versionFile) { 22 | Wrapper1_18_1.versionFile = versionFile; 23 | 24 | Wrappers.DESTROY_W_CONTAINER = DestroyWrapper::new; 25 | Wrappers.DESTROY = DestroyWrapper::new; 26 | Wrappers.METADATA_W_CONTAINER = MetaDataWrapper::new; 27 | Wrappers.METADATA = MetaDataWrapper::new; 28 | Wrappers.MOUNT = MountWrapper::new; 29 | Wrappers.SPAWN_ENTITY = SpawnEntityWrapper::new; 30 | Wrappers.SPAWN_ENTITY_LIVING = SpawnEntityLivingWrapper::new; 31 | 32 | Wrappers.packetTypes = new PacketType[]{SPAWN_ENTITY, SPAWN_ENTITY_LIVING, ENTITY_DESTROY, NAMED_ENTITY_SPAWN, ENTITY_METADATA, PLAYER_INFO, REL_ENTITY_MOVE, REL_ENTITY_MOVE_LOOK, ENTITY_LOOK, ENTITY_TELEPORT}; 33 | } 34 | 35 | public static class DestroyWrapper extends Wrappers.DestroyPacket { 36 | 37 | public DestroyWrapper() { 38 | super(); 39 | } 40 | 41 | public DestroyWrapper(PacketContainer packet) { 42 | super(packet); 43 | } 44 | 45 | @Override 46 | public int getCount() { 47 | return handle.getIntLists().read(0).size(); 48 | } 49 | 50 | @Override 51 | public List getEntityIDs() { 52 | return handle.getIntLists().read(0); 53 | } 54 | 55 | @Override 56 | public void addEntityID(int id) { 57 | List ids = this.getEntityIDs(); 58 | ids.add(id); 59 | handle.getIntLists().write(0, ids); 60 | } 61 | } 62 | 63 | public static class MetaDataWrapper extends Wrappers.MetaDataPacket { 64 | 65 | public MetaDataWrapper() { 66 | super(); 67 | } 68 | 69 | public MetaDataWrapper(PacketContainer packet) { 70 | super(packet); 71 | } 72 | 73 | @Override 74 | public void setEntityID(int value) { 75 | handle.getIntegers().write(0, value); 76 | } 77 | 78 | @Override 79 | public List getMetadata() { 80 | return handle.getWatchableCollectionModifier().read(0); 81 | } 82 | 83 | @Override 84 | public void setMetadata(List value) { 85 | handle.getWatchableCollectionModifier().write(0, value); 86 | } 87 | } 88 | 89 | public static class MountWrapper extends Wrappers.MountPacket { 90 | 91 | @Override 92 | public void setEntityID(int value) { 93 | handle.getIntegers().write(0, value); 94 | } 95 | 96 | @Override 97 | public int[] getPassengerIds() { 98 | return handle.getIntegerArrays().read(0); 99 | } 100 | 101 | @Override 102 | public void setPassengerIds(int[] value) { 103 | handle.getIntegerArrays().write(0, value); 104 | } 105 | } 106 | 107 | public static class SpawnEntityWrapper extends Wrappers.SpawnEntityPacket { 108 | 109 | @Override 110 | public void setID(int entityID) { 111 | handle.getIntegers().write(0, entityID); 112 | } 113 | 114 | @Override 115 | public void setUUID(UUID uuid) { 116 | handle.getUUIDs().write(0, uuid); 117 | } 118 | 119 | @Override 120 | public void setType(EntityType type) { 121 | handle.getEntityTypeModifier().write(0, type); 122 | } 123 | 124 | @Override 125 | public void setLocation(Location location) { 126 | this.setX(location.getX()); 127 | this.setY(location.getY()); 128 | this.setZ(location.getZ()); 129 | } 130 | 131 | @Override 132 | public void setYaw(float value) { 133 | handle.getIntegers().write(5, (int) (value * 256.0F / 360.0F)); 134 | } 135 | 136 | @Override 137 | public void setHeadYaw(float value) { 138 | } 139 | 140 | @Override 141 | public void setPitch(float value) { 142 | handle.getIntegers().write(4, (int) (value * 256.0F / 360.0F)); 143 | } 144 | 145 | @Override 146 | public void setVelocityX(int value) { 147 | handle.getIntegers().write(1, (int) (value * 8000.0D)); 148 | } 149 | 150 | @Override 151 | public void setVelocityY(int value) { 152 | handle.getIntegers().write(2, (int) (value * 8000.0D)); 153 | } 154 | 155 | @Override 156 | public void setVelocityZ(int value) { 157 | handle.getIntegers().write(3, (int) (value * 8000.0D)); 158 | } 159 | 160 | @Override 161 | public void setX(double value) { 162 | handle.getDoubles().write(0, value); 163 | } 164 | 165 | @Override 166 | public void setY(double value) { 167 | handle.getDoubles().write(1, value); 168 | } 169 | 170 | @Override 171 | public void setZ(double value) { 172 | handle.getDoubles().write(2, value); 173 | } 174 | 175 | @Override 176 | public void setObjectData(int value) { 177 | handle.getIntegers().write(6, value); 178 | } 179 | } 180 | 181 | public static class SpawnEntityLivingWrapper extends Wrappers.SpawnEntityLivingPacket { 182 | 183 | @Override 184 | public void setID(int entityID) { 185 | this.getHandle().getIntegers().write(0, entityID); 186 | } 187 | 188 | @Override 189 | public void setUUID(UUID uuid) { 190 | this.getHandle().getUUIDs().write(0, uuid); 191 | } 192 | 193 | @Override 194 | public void setType(EntityType type) { 195 | handle.getIntegers().write(1, versionFile.getEntityID(type)); 196 | } 197 | 198 | @Override 199 | public void setLocation(Location location) { 200 | handle.getDoubles().write(0, location.getX()); 201 | handle.getDoubles().write(1, location.getY()); 202 | handle.getDoubles().write(2, location.getZ()); 203 | } 204 | 205 | @Override 206 | public void setYaw(float value) { 207 | handle.getBytes().write(0, (byte) (value * 256.0F / 360.0F)); 208 | } 209 | 210 | @Override 211 | public void setPitch(float value) { 212 | handle.getBytes().write(1, (byte) (value * 256.0F / 360.0F)); 213 | } 214 | 215 | @Override 216 | public void setVelocityX(int value) { 217 | handle.getIntegers().write(2, value); 218 | } 219 | 220 | @Override 221 | public void setVelocityY(int value) { 222 | handle.getIntegers().write(3, value); 223 | } 224 | 225 | @Override 226 | public void setVelocityZ(int value) { 227 | handle.getIntegers().write(4, value); 228 | } 229 | 230 | @Override 231 | public void setHeadYaw(float value) { 232 | handle.getBytes().write(2, (byte) (value * 256.0F / 360.0F)); 233 | } 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/main/java/com/lkeehl/tagapi/wrappers/versions/Wrapper1_19_1.java: -------------------------------------------------------------------------------- 1 | package com.lkeehl.tagapi.wrappers.versions; 2 | 3 | import com.comphenix.protocol.PacketType; 4 | import com.comphenix.protocol.events.PacketContainer; 5 | import com.comphenix.protocol.wrappers.WrappedWatchableObject; 6 | import com.lkeehl.tagapi.util.VersionFile; 7 | import com.lkeehl.tagapi.wrappers.Wrappers; 8 | import org.bukkit.Location; 9 | import org.bukkit.entity.EntityType; 10 | 11 | import java.util.List; 12 | import java.util.UUID; 13 | 14 | import static com.comphenix.protocol.PacketType.Play.Server.*; 15 | import static com.comphenix.protocol.PacketType.Play.Server.ENTITY_TELEPORT; 16 | 17 | public class Wrapper1_19_1 { 18 | 19 | private static VersionFile versionFile; 20 | 21 | public static void init(VersionFile versionFile) { 22 | Wrapper1_19_1.versionFile = versionFile; 23 | 24 | Wrappers.DESTROY_W_CONTAINER = DestroyWrapper::new; 25 | Wrappers.DESTROY = DestroyWrapper::new; 26 | Wrappers.METADATA_W_CONTAINER = MetaDataWrapper::new; 27 | Wrappers.METADATA = MetaDataWrapper::new; 28 | Wrappers.MOUNT = MountWrapper::new; 29 | Wrappers.SPAWN_ENTITY = SpawnEntityWrapper::new; 30 | Wrappers.SPAWN_ENTITY_LIVING = SpawnEntityWrapper::new; 31 | 32 | Wrappers.packetTypes = new PacketType[]{SPAWN_ENTITY, ENTITY_DESTROY, NAMED_ENTITY_SPAWN, ENTITY_METADATA, PLAYER_INFO, REL_ENTITY_MOVE, REL_ENTITY_MOVE_LOOK, ENTITY_LOOK, ENTITY_TELEPORT}; 33 | } 34 | 35 | public static class DestroyWrapper extends Wrappers.DestroyPacket { 36 | 37 | public DestroyWrapper() { 38 | super(); 39 | } 40 | 41 | public DestroyWrapper(PacketContainer packet) { 42 | super(packet); 43 | } 44 | 45 | @Override 46 | public int getCount() { 47 | return handle.getIntLists().read(0).size(); 48 | } 49 | 50 | @Override 51 | public List getEntityIDs() { 52 | return handle.getIntLists().read(0); 53 | } 54 | 55 | @Override 56 | public void addEntityID(int id) { 57 | List ids = this.getEntityIDs(); 58 | ids.add(id); 59 | handle.getIntLists().write(0, ids); 60 | } 61 | } 62 | 63 | public static class MetaDataWrapper extends Wrappers.MetaDataPacket { 64 | 65 | public MetaDataWrapper() { 66 | super(); 67 | } 68 | 69 | public MetaDataWrapper(PacketContainer packet) { 70 | super(packet); 71 | } 72 | 73 | @Override 74 | public void setEntityID(int value) { 75 | handle.getIntegers().write(0, value); 76 | } 77 | 78 | @Override 79 | public List getMetadata() { 80 | return handle.getWatchableCollectionModifier().read(0); 81 | } 82 | 83 | @Override 84 | public void setMetadata(List value) { 85 | handle.getWatchableCollectionModifier().write(0, value); 86 | } 87 | } 88 | 89 | public static class MountWrapper extends Wrappers.MountPacket { 90 | 91 | @Override 92 | public void setEntityID(int value) { 93 | handle.getIntegers().write(0, value); 94 | } 95 | 96 | @Override 97 | public int[] getPassengerIds() { 98 | return handle.getIntegerArrays().read(0); 99 | } 100 | 101 | @Override 102 | public void setPassengerIds(int[] value) { 103 | handle.getIntegerArrays().write(0, value); 104 | } 105 | } 106 | 107 | public static class SpawnEntityWrapper extends Wrappers.SpawnEntityPacket { 108 | 109 | @Override 110 | public void setID(int entityID) { 111 | handle.getIntegers().write(0, entityID); 112 | } 113 | 114 | @Override 115 | public void setUUID(UUID uuid) { 116 | handle.getUUIDs().write(0, uuid); 117 | } 118 | 119 | @Override 120 | public void setType(EntityType type) { 121 | handle.getEntityTypeModifier().write(0, type); 122 | } 123 | 124 | @Override 125 | public void setLocation(Location location) { 126 | this.setX(location.getX()); 127 | this.setY(location.getY()); 128 | this.setZ(location.getZ()); 129 | } 130 | 131 | @Override 132 | public void setVelocityX(int value) { 133 | handle.getIntegers().write(1, (int) (value * 8000.0D)); 134 | } 135 | 136 | @Override 137 | public void setVelocityY(int value) { 138 | handle.getIntegers().write(2, (int) (value * 8000.0D)); 139 | } 140 | 141 | @Override 142 | public void setVelocityZ(int value) { 143 | handle.getIntegers().write(3, (int) (value * 8000.0D)); 144 | } 145 | 146 | @Override 147 | public void setX(double value) { 148 | handle.getDoubles().write(0, value); 149 | } 150 | 151 | @Override 152 | public void setY(double value) { 153 | handle.getDoubles().write(1, value); 154 | } 155 | 156 | @Override 157 | public void setZ(double value) { 158 | handle.getDoubles().write(2, value); 159 | } 160 | 161 | @Override 162 | public void setObjectData(int value) { 163 | handle.getIntegers().write(4, value); 164 | } 165 | 166 | @Override 167 | public void setYaw(float value) { 168 | handle.getBytes().write(0, (byte) (value * 256.0F / 360.0F)); 169 | } 170 | 171 | @Override 172 | public void setPitch(float value) { 173 | handle.getBytes().write(1, (byte) (value * 256.0F / 360.0F)); 174 | } 175 | 176 | @Override 177 | public void setHeadYaw(float value) { 178 | handle.getBytes().write(2, (byte) (value * 256.0F / 360.0F)); 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: TagAPI 2 | version: 1.2.3 3 | author: Keehl 4 | main: com.lkeehl.tagapi.manager.TagManager 5 | api-version: '1.13' 6 | depend: [ProtocolLib] -------------------------------------------------------------------------------- /src/main/resources/versions.cult: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keehl254/TagAPI/fb03f0e38243374af77a51e5373ea0b909069e77/src/main/resources/versions.cult --------------------------------------------------------------------------------