├── .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 |
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 |
8 |
Using a scoreboard
9 |
Teleporting an invisible armor stand to the players location
10 |
Using an amalgamation of invisible entities and slimes with a -1 size mounting the player
11 |
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 |
28 |
Spawn Entity - Tell the client to spawn the entity
29 |
Entity Metadata - Set data about the entity, such as invisibility
30 |
Mount entity - Attach the entities together
31 |
Destroy entity - Despawn the entities when the target entity leaves view, dies, or quits
32 |
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 |
41 |
Health bar on entities while attacking
42 |
A mark showing a players party or faction
43 |
A line that states if a player is AFK
44 |
A titles system like mentioned in the first paragraph
45 |
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