├── .gitignore
├── build.xml
└── src
├── plugin.yml
└── tickoptimizer
├── InjectorListener.java
├── ServerInjector.java
├── TickOptimizer.java
├── usercache
├── DualCache.java
├── GameProfileLookup.java
├── OptimizedUserCache.java
├── UserCacheEntryJsonSerializer.java
├── UserCacheEntryType.java
└── UserCacheFileEntry.java
├── utils
├── Utils.java
└── collections
│ ├── HashSetBackedArrayList.java
│ └── HashSetFakeListImpl.java
└── world
├── OptimizedNavigationListener.java
├── WorldInjector.java
├── block
└── InjectTEBlockBeacon.java
└── tileentity
└── OptimizedTileEntityBeacon.java
/.gitignore:
--------------------------------------------------------------------------------
1 | # Build Folders #
2 | bin/
3 | build/
4 | target/
5 | libs/
6 | .externalToolBuilders/
7 | # Eclipse Files #
8 | *.project
9 | *.classpath
10 | *.prefs
11 |
--------------------------------------------------------------------------------
/build.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/plugin.yml:
--------------------------------------------------------------------------------
1 | name: TickOptimizer
2 | main: tickoptimizer.TickOptimizer
3 | version: 1.0
4 | author: _Shevchik_
5 | load: STARTUP
--------------------------------------------------------------------------------
/src/tickoptimizer/InjectorListener.java:
--------------------------------------------------------------------------------
1 | package tickoptimizer;
2 |
3 | import org.bukkit.event.EventHandler;
4 | import org.bukkit.event.EventPriority;
5 | import org.bukkit.event.Listener;
6 | import org.bukkit.event.world.WorldInitEvent;
7 |
8 | import tickoptimizer.world.WorldInjector;
9 |
10 | public class InjectorListener implements Listener {
11 |
12 | @EventHandler(priority=EventPriority.LOWEST)
13 | public void onWorldInit(WorldInitEvent event) {
14 | WorldInjector.inject(event.getWorld());
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/tickoptimizer/ServerInjector.java:
--------------------------------------------------------------------------------
1 | package tickoptimizer;
2 |
3 | import java.io.File;
4 | import java.lang.reflect.Field;
5 | import java.util.Iterator;
6 | import java.util.Map;
7 |
8 | import net.minecraft.server.v1_12_R1.Block;
9 | import net.minecraft.server.v1_12_R1.Blocks;
10 | import net.minecraft.server.v1_12_R1.IBlockData;
11 | import net.minecraft.server.v1_12_R1.Item;
12 | import net.minecraft.server.v1_12_R1.ItemBlock;
13 | import net.minecraft.server.v1_12_R1.MinecraftKey;
14 | import net.minecraft.server.v1_12_R1.MinecraftServer;
15 | import net.minecraft.server.v1_12_R1.RegistryMaterials;
16 | import net.minecraft.server.v1_12_R1.TileEntity;
17 | import net.minecraft.server.v1_12_R1.UserCache;
18 |
19 | import org.bukkit.Bukkit;
20 |
21 | import tickoptimizer.usercache.OptimizedUserCache;
22 | import tickoptimizer.utils.Utils;
23 | import tickoptimizer.world.block.InjectTEBlockBeacon;
24 | import tickoptimizer.world.tileentity.OptimizedTileEntityBeacon;
25 |
26 | public class ServerInjector {
27 |
28 | @SuppressWarnings("deprecation")
29 | public static void injectUserCache() throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
30 | UserCache oldUserCache = MinecraftServer.getServer().getUserCache();
31 | File oldUserCacheFile = (File) Utils.setAccessible(oldUserCache.getClass().getDeclaredField("h")).get(oldUserCache);
32 | OptimizedUserCache newUserCache = new OptimizedUserCache(MinecraftServer.getServer().getGameProfileRepository(), oldUserCacheFile);
33 | Utils.setFinalField(MinecraftServer.class.getDeclaredField("Y"), MinecraftServer.getServer(), newUserCache);
34 | }
35 |
36 | public static void injectRegistry() throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
37 | registerTileEntity("beacon", OptimizedTileEntityBeacon.class);
38 | registerBlock(138, "beacon", new InjectTEBlockBeacon());
39 |
40 | fixBlocksRefs();
41 |
42 | Bukkit.resetRecipes();
43 | }
44 |
45 | @SuppressWarnings("unchecked")
46 | private static void registerTileEntity(String name, Class extends TileEntity> entityClass) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
47 | RegistryMaterials> registry = (RegistryMaterials>) Utils.getFieldValue(null, TileEntity.class, "f");
48 | registry.a(new MinecraftKey(name), entityClass);
49 | }
50 |
51 | @SuppressWarnings("unchecked")
52 | private static void registerBlock(int id, String name, Block block) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
53 | MinecraftKey stringkey = new MinecraftKey(name);
54 | ItemBlock itemblock = new ItemBlock(block);
55 | Block.REGISTRY.a(id, stringkey, block);
56 | Iterator blockdataiterator = block.s().a().iterator();
57 | while (blockdataiterator.hasNext()) {
58 | IBlockData blockdata = blockdataiterator.next();
59 | final int stateId = (id << 4) | block.toLegacyData(blockdata);
60 | Block.REGISTRY_ID.a(blockdata, stateId);
61 | }
62 | Item.REGISTRY.a(id, stringkey, itemblock);
63 | ((Map)Utils.setAccessible(Item.class.getDeclaredField("a")).get(null)).put(block, itemblock);
64 | }
65 |
66 | private static void fixBlocksRefs() throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
67 | for (Field field : Blocks.class.getDeclaredFields()) {
68 | field.setAccessible(true);
69 | if (Block.class.isAssignableFrom(field.getType())) {
70 | Block block = (Block) field.get(null);
71 | Block newblock = Block.getById(Block.getId(block));
72 | if (block != newblock) {
73 | Iterator blockdataiterator = block.s().a().iterator();
74 | while (blockdataiterator.hasNext()) {
75 | IBlockData blockdata = blockdataiterator.next();
76 | Utils.setFieldValue(blockdata, "a", block);
77 | }
78 | Utils.setFinalField(field, null, newblock);
79 | }
80 | }
81 | }
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/src/tickoptimizer/TickOptimizer.java:
--------------------------------------------------------------------------------
1 | package tickoptimizer;
2 |
3 | import net.minecraft.server.v1_12_R1.MinecraftServer;
4 |
5 | import org.bukkit.Bukkit;
6 | import org.bukkit.plugin.java.JavaPlugin;
7 |
8 | public class TickOptimizer extends JavaPlugin {
9 |
10 | @Override
11 | public void onLoad() {
12 | try {
13 | ServerInjector.injectUserCache();
14 | ServerInjector.injectRegistry();
15 | } catch (Throwable t) {
16 | t.printStackTrace();
17 | Bukkit.shutdown();
18 | }
19 | }
20 |
21 | @Override
22 | public void onEnable() {
23 | getServer().getPluginManager().registerEvents(new InjectorListener(), this);
24 | }
25 |
26 | @SuppressWarnings("deprecation")
27 | @Override
28 | public void onDisable() {
29 | MinecraftServer.getServer().getUserCache().c();
30 | Bukkit.shutdown();
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/tickoptimizer/usercache/DualCache.java:
--------------------------------------------------------------------------------
1 | package tickoptimizer.usercache;
2 |
3 | import java.util.ArrayList;
4 | import java.util.HashMap;
5 | import java.util.LinkedHashMap;
6 | import java.util.List;
7 | import java.util.Map;
8 | import java.util.concurrent.locks.ReentrantReadWriteLock;
9 | import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
10 | import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
11 |
12 | public class DualCache {
13 |
14 | private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
15 | private final ReadLock rlock = lock.readLock();
16 | private final WriteLock wlock = lock.writeLock();
17 |
18 | private final long validtime;
19 | private final int limit;
20 |
21 | private final Map> primary;
22 | private final Map> secondary;
23 |
24 | public DualCache(int limit, long validtime) {
25 | this.limit = limit;
26 | this.validtime = validtime;
27 | int size = (int) (limit * 1.5);
28 | primary = new LinkedHashMap<>(size, 0.75F, true);
29 | secondary = new HashMap<>(size);
30 | }
31 |
32 | public V getByPrimaryKey(Object primaryKey) {
33 | rlock.lock();
34 | try {
35 | DualCacheEntry entry = primary.get(primaryKey);
36 | return entry != null ? entry.value : null;
37 | } finally {
38 | rlock.unlock();
39 | }
40 | }
41 |
42 | public V getBySecondaryKey(Object secondaryKey) {
43 | DualCacheEntry entry = null;
44 | rlock.lock();
45 | try {
46 | entry = secondary.get(secondaryKey);
47 | } finally {
48 | rlock.unlock();
49 | }
50 | if (entry != null) {
51 | if (System.currentTimeMillis() > entry.expiretime) {
52 | remove(entry.pk);
53 | return null;
54 | } else {
55 | getByPrimaryKey(entry.pk);
56 | return entry.value;
57 | }
58 | } else {
59 | return null;
60 | }
61 | }
62 |
63 | public void put(PK primaryKey, SK secondaryKey, V value) {
64 | wlock.lock();
65 | try {
66 | DualCacheEntry oldEntry = primary.get(primaryKey);
67 | if (oldEntry != null) {
68 | secondary.remove(oldEntry.sk);
69 | }
70 | DualCacheEntry newEntry = new DualCacheEntry<>(primaryKey, secondaryKey, value, System.currentTimeMillis() + validtime);
71 | primary.put(primaryKey, newEntry);
72 | secondary.put(secondaryKey, newEntry);
73 | if (primary.size() > limit) {
74 | remove(primary.keySet().iterator().next());
75 | }
76 | } finally {
77 | wlock.unlock();
78 | }
79 | }
80 |
81 | public void remove(Object primaryKey) {
82 | wlock.lock();
83 | try {
84 | DualCacheEntry entry = primary.remove(primaryKey);
85 | if (entry != null) {
86 | secondary.remove(entry.sk);
87 | }
88 | } finally {
89 | wlock.unlock();
90 | }
91 | }
92 |
93 | public List getPrimaryKeys() {
94 | rlock.lock();
95 | try {
96 | return new ArrayList<>(primary.keySet());
97 | } finally {
98 | rlock.unlock();
99 | }
100 | }
101 |
102 | public List getSecondaryKeys() {
103 | rlock.lock();
104 | try {
105 | return new ArrayList<>(secondary.keySet());
106 | } finally {
107 | rlock.unlock();
108 | }
109 | }
110 |
111 | public void clear() {
112 | wlock.lock();
113 | try {
114 | primary.clear();
115 | secondary.clear();
116 | } finally {
117 | wlock.unlock();
118 | }
119 | }
120 |
121 | public void putLoaded(PK primaryKey, SK secondaryKey, V value, long expiretime) {
122 | wlock.lock();
123 | try {
124 | DualCacheEntry newEntry = new DualCacheEntry<>(primaryKey, secondaryKey, value, System.currentTimeMillis() + expiretime);
125 | primary.put(primaryKey, newEntry);
126 | secondary.put(secondaryKey, newEntry);
127 | } finally {
128 | wlock.unlock();
129 | }
130 | }
131 |
132 | public List> getEntries() {
133 | rlock.lock();
134 | try {
135 | return new ArrayList<>(primary.values());
136 | } finally {
137 | rlock.unlock();
138 | }
139 | }
140 |
141 | public static class DualCacheEntry {
142 | protected PK pk;
143 | protected SK sk;
144 | protected V value;
145 | protected long expiretime;
146 | public DualCacheEntry(PK pk, SK sk, V v, long expiretime) {
147 | this.pk = pk;
148 | this.sk = sk;
149 | this.value = v;
150 | this.expiretime = expiretime;
151 | }
152 |
153 | public PK getPrimaryKey() {
154 | return pk;
155 | }
156 |
157 | public SK getSecondaryKey() {
158 | return sk;
159 | }
160 |
161 | public V getValue() {
162 | return value;
163 | }
164 |
165 | public long getExpireDate() {
166 | return expiretime;
167 | }
168 |
169 | @Override
170 | public String toString() {
171 | return
172 | "PK: "+getPrimaryKey()+", "+
173 | "SK: "+getSecondaryKey()+", "+
174 | "Expire: "+getExpireDate()+", "+
175 | "Value: "+getValue();
176 | }
177 | }
178 |
179 | }
180 |
--------------------------------------------------------------------------------
/src/tickoptimizer/usercache/GameProfileLookup.java:
--------------------------------------------------------------------------------
1 | package tickoptimizer.usercache;
2 |
3 | import com.mojang.authlib.GameProfile;
4 | import com.mojang.authlib.ProfileLookupCallback;
5 |
6 | public class GameProfileLookup implements ProfileLookupCallback {
7 |
8 | private GameProfile[] gameProfileHolderArray;
9 |
10 | public GameProfileLookup(GameProfile[] gameProfileHolderArray) {
11 | this.gameProfileHolderArray = gameProfileHolderArray;
12 | }
13 |
14 | @Override
15 | public void onProfileLookupFailed(GameProfile profile, Exception exception) {
16 | this.gameProfileHolderArray[0] = null;
17 | }
18 |
19 | @Override
20 | public void onProfileLookupSucceeded(GameProfile profile) {
21 | this.gameProfileHolderArray[0] = profile;
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/tickoptimizer/usercache/OptimizedUserCache.java:
--------------------------------------------------------------------------------
1 | package tickoptimizer.usercache;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.BufferedWriter;
5 | import java.io.File;
6 | import java.io.IOException;
7 | import java.util.ArrayList;
8 | import java.util.Date;
9 | import java.util.List;
10 | import java.util.UUID;
11 |
12 | import org.spigotmc.SpigotConfig;
13 |
14 | import com.google.common.base.Charsets;
15 | import com.google.common.io.Files;
16 | import com.google.gson.Gson;
17 | import com.google.gson.GsonBuilder;
18 | import com.mojang.authlib.Agent;
19 | import com.mojang.authlib.GameProfile;
20 | import com.mojang.authlib.GameProfileRepository;
21 |
22 | import net.minecraft.server.v1_12_R1.EntityHuman;
23 | import net.minecraft.server.v1_12_R1.MinecraftServer;
24 | import net.minecraft.server.v1_12_R1.UserCache;
25 | import tickoptimizer.usercache.DualCache.DualCacheEntry;
26 |
27 | public class OptimizedUserCache extends UserCache {
28 |
29 | private static final Gson GSON = new GsonBuilder().registerTypeHierarchyAdapter(UserCacheFileEntry.class, new UserCacheEntryJsonSerializer()).create();
30 | private static final UserCacheEntryType type = new UserCacheEntryType();
31 |
32 | private final DualCache cache = new DualCache(SpigotConfig.userCacheCap, 1000L * 60L * 60L * 24L * 30L);
33 | private final File userCacheFile;
34 |
35 | public OptimizedUserCache(GameProfileRepository repo, File file) {
36 | super(repo, file);
37 | this.userCacheFile = file;
38 | b();
39 | }
40 |
41 | @Override
42 | public void a(GameProfile gameProfile) {
43 | cache.put(gameProfile.getId(), gameProfile.getName(), gameProfile);
44 | }
45 |
46 | @SuppressWarnings("deprecation")
47 | @Override
48 | public GameProfile getProfile(String playername) {
49 | GameProfile profile = cache.getBySecondaryKey(playername);
50 | if (profile != null) {
51 | return profile;
52 | }
53 | profile = lookupProfile(MinecraftServer.getServer(), playername);
54 | if (profile != null) {
55 | a(profile);
56 | return profile;
57 | }
58 | return null;
59 | }
60 |
61 | @Override
62 | public GameProfile a(UUID uuid) {
63 | return cache.getByPrimaryKey(uuid);
64 | }
65 |
66 | @Override
67 | public String[] a() {
68 | List list = cache.getSecondaryKeys();
69 | return list.toArray(new String[list.size()]);
70 | }
71 |
72 | @Override
73 | public void c() {
74 | ArrayList list = new ArrayList();
75 | for (DualCacheEntry entry : cache.getEntries()) {
76 | list.add(new UserCacheFileEntry(entry.getValue(), new Date(entry.getExpireDate())));
77 | }
78 | String data = GSON.toJson(list);
79 | try (BufferedWriter writer = Files.newWriter(userCacheFile, Charsets.UTF_8)) {
80 | writer.write(data);
81 | } catch (IOException e) {
82 | userCacheFile.delete();
83 | }
84 | }
85 |
86 | @Override
87 | public void b() {
88 | if (userCacheFile == null) {
89 | return;
90 | }
91 | try (BufferedReader reader = Files.newReader(userCacheFile, Charsets.UTF_8)) {
92 | List datalist = GSON.fromJson(reader, type);
93 | cache.clear();
94 | for (UserCacheFileEntry entry : datalist) {
95 | cache.putLoaded(entry.getProfile().getId(), entry.getProfile().getName(), entry.getProfile(), entry.getExpireDate());
96 | }
97 | } catch (IOException exception) {
98 | }
99 | }
100 |
101 | private static GameProfile lookupProfile(MinecraftServer minecraftserver, String name) {
102 | GameProfile[] gameProfileArrayHolder = new GameProfile[1];
103 | GameProfileLookup gameProfileLookup = new GameProfileLookup(gameProfileArrayHolder);
104 | minecraftserver.getGameProfileRepository().findProfilesByNames(new String[] { name }, Agent.MINECRAFT, gameProfileLookup);
105 | if ((!minecraftserver.getOnlineMode()) && (gameProfileArrayHolder[0] == null)) {
106 | UUID uuid = EntityHuman.a(new GameProfile(null, name));
107 | GameProfile gameprofile = new GameProfile(uuid, name);
108 | gameProfileLookup.onProfileLookupSucceeded(gameprofile);
109 | }
110 | return gameProfileArrayHolder[0];
111 | }
112 |
113 | }
114 |
--------------------------------------------------------------------------------
/src/tickoptimizer/usercache/UserCacheEntryJsonSerializer.java:
--------------------------------------------------------------------------------
1 | package tickoptimizer.usercache;
2 |
3 | import java.lang.reflect.Type;
4 | import java.text.ParseException;
5 | import java.util.Date;
6 | import java.util.UUID;
7 |
8 | import net.minecraft.server.v1_12_R1.UserCache;
9 |
10 | import com.google.gson.JsonDeserializationContext;
11 | import com.google.gson.JsonDeserializer;
12 | import com.google.gson.JsonElement;
13 | import com.google.gson.JsonObject;
14 | import com.google.gson.JsonParseException;
15 | import com.google.gson.JsonSerializationContext;
16 | import com.google.gson.JsonSerializer;
17 | import com.mojang.authlib.GameProfile;
18 |
19 | public class UserCacheEntryJsonSerializer implements JsonDeserializer, JsonSerializer {
20 |
21 | @Override
22 | public JsonElement serialize(UserCacheFileEntry entry, Type type, JsonSerializationContext ctx) {
23 | JsonObject jsonobject = new JsonObject();
24 | jsonobject.addProperty("name", entry.getProfile().getName());
25 | UUID uuid = entry.getProfile().getId();
26 | jsonobject.addProperty("uuid", uuid == null ? "" : uuid.toString());
27 | jsonobject.addProperty("expiresOn", UserCache.a.format(entry.getExpireDate()));
28 | return jsonobject;
29 | }
30 |
31 | @Override
32 | public UserCacheFileEntry deserialize(JsonElement element, Type type, JsonDeserializationContext ctx) throws JsonParseException {
33 | if (element.isJsonObject()) {
34 | JsonObject jsonobject = element.getAsJsonObject();
35 | JsonElement nameElement = jsonobject.get("name");
36 | JsonElement uuidElement = jsonobject.get("uuid");
37 | JsonElement expireDateElement = jsonobject.get("expiresOn");
38 | if ((nameElement != null) && (uuidElement != null)) {
39 | String name = uuidElement.getAsString();
40 | String uuidstring = nameElement.getAsString();
41 | Date date = null;
42 | if (expireDateElement != null) {
43 | try {
44 | date = UserCache.a.parse(expireDateElement.getAsString());
45 | } catch (ParseException parseexception) {
46 | date = null;
47 | }
48 | }
49 | if ((uuidstring != null) && (name != null)) {
50 | UUID uuid;
51 | try {
52 | uuid = UUID.fromString(name);
53 | } catch (Throwable throwable) {
54 | return null;
55 | }
56 | UserCacheFileEntry usercacheentry = new UserCacheFileEntry(new GameProfile(uuid, uuidstring), date);
57 |
58 | return usercacheentry;
59 | }
60 | return null;
61 | }
62 | return null;
63 | }
64 | return null;
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/src/tickoptimizer/usercache/UserCacheEntryType.java:
--------------------------------------------------------------------------------
1 | package tickoptimizer.usercache;
2 |
3 | import java.lang.reflect.ParameterizedType;
4 | import java.lang.reflect.Type;
5 | import java.util.List;
6 |
7 | public class UserCacheEntryType implements ParameterizedType {
8 |
9 | @Override
10 | public Type[] getActualTypeArguments() {
11 | return new Type[] {
12 | UserCacheFileEntry.class
13 | };
14 | }
15 |
16 | @Override
17 | public Type getRawType() {
18 | return List.class;
19 | }
20 |
21 | @Override
22 | public Type getOwnerType() {
23 | return null;
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/tickoptimizer/usercache/UserCacheFileEntry.java:
--------------------------------------------------------------------------------
1 | package tickoptimizer.usercache;
2 |
3 | import java.util.Date;
4 |
5 | import com.mojang.authlib.GameProfile;
6 |
7 | public class UserCacheFileEntry {
8 |
9 | private GameProfile profile;
10 | private Date expireDate;
11 |
12 | public UserCacheFileEntry(GameProfile profile, Date date) {
13 | this.profile = profile;
14 | this.expireDate = date;
15 | }
16 |
17 | public long getExpireDate() {
18 | return expireDate.getTime();
19 | }
20 |
21 | public GameProfile getProfile() {
22 | return profile;
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/tickoptimizer/utils/Utils.java:
--------------------------------------------------------------------------------
1 | package tickoptimizer.utils;
2 |
3 | import java.lang.invoke.MethodHandle;
4 | import java.lang.invoke.MethodHandles;
5 | import java.lang.invoke.MethodType;
6 | import java.lang.reflect.AccessibleObject;
7 | import java.lang.reflect.Field;
8 | import java.lang.reflect.Modifier;
9 |
10 | import org.bukkit.Bukkit;
11 |
12 | public class Utils {
13 |
14 | public static T setAccessible(T object) {
15 | object.setAccessible(true);
16 | return object;
17 | }
18 |
19 | public static void setFinalField(Field field, Object obj, Object newValue) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
20 | setAccessible(Field.class.getDeclaredField("modifiers")).setInt(field, field.getModifiers() & ~Modifier.FINAL);
21 | setAccessible(Field.class.getDeclaredField("root")).set(field, null);
22 | setAccessible(Field.class.getDeclaredField("overrideFieldAccessor")).set(field, null);
23 | setAccessible(field).set(obj, newValue);
24 | }
25 |
26 | public static MethodHandle getFieldSetter(Class> classIn, String fieldName, Class> newFieldClass) {
27 | try {
28 | return MethodHandles
29 | .lookup()
30 | .unreflectSetter(setAccessible(classIn.getDeclaredField(fieldName)))
31 | .asType(MethodType.methodType(void.class, classIn, newFieldClass));
32 | } catch (Throwable t) {
33 | t.printStackTrace();
34 | Bukkit.shutdown();
35 | }
36 | return null;
37 | }
38 |
39 | @SuppressWarnings("unchecked")
40 | public static V getFieldValue(Object obj, Class> classIn, String fieldName) {
41 | try {
42 | return (V) setAccessible(classIn.getDeclaredField(fieldName)).get(obj);
43 | } catch (Throwable t) {
44 | t.printStackTrace();
45 | Bukkit.shutdown();
46 | }
47 | return null;
48 | }
49 |
50 | public static void setFieldValue(Object obj, String name, Object newValue) throws IllegalArgumentException, IllegalAccessException {
51 | Class> clazz = obj.getClass();
52 | do {
53 | for (Field field : clazz.getDeclaredFields()) {
54 | if (field.getName().equals(name)) {
55 | setAccessible(field).set(obj, newValue);
56 | return;
57 | }
58 | }
59 | } while ((clazz = clazz.getSuperclass()) != null);
60 | throw new RuntimeException("Can't find field "+name);
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/src/tickoptimizer/utils/collections/HashSetBackedArrayList.java:
--------------------------------------------------------------------------------
1 | package tickoptimizer.utils.collections;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collection;
5 | import java.util.Iterator;
6 | import java.util.List;
7 | import java.util.ListIterator;
8 | import java.util.NoSuchElementException;
9 |
10 | import com.google.common.collect.HashMultiset;
11 |
12 | public class HashSetBackedArrayList implements List {
13 |
14 | protected final ArrayList arraylist = new ArrayList();
15 | protected final HashMultiset hashset = HashMultiset.create();
16 |
17 | @Override
18 | public int size() {
19 | return arraylist.size();
20 | }
21 |
22 | @Override
23 | public boolean isEmpty() {
24 | return arraylist.isEmpty();
25 | }
26 |
27 | @Override
28 | public boolean contains(Object o) {
29 | return hashset.contains(o);
30 | }
31 |
32 | @Override
33 | public Object[] toArray() {
34 | return arraylist.toArray();
35 | }
36 |
37 | @Override
38 | public T[] toArray(T[] a) {
39 | return arraylist.toArray(a);
40 | }
41 |
42 | @Override
43 | public boolean add(E e) {
44 | arraylist.add(e);
45 | hashset.add(e);
46 | return true;
47 | }
48 |
49 | @Override
50 | public boolean containsAll(Collection> c) {
51 | return hashset.containsAll(c);
52 | }
53 |
54 | @Override
55 | public boolean addAll(Collection extends E> c) {
56 | arraylist.addAll(c);
57 | hashset.addAll(c);
58 | return true;
59 | }
60 |
61 | @Override
62 | public boolean removeAll(Collection> c) {
63 | boolean result = arraylist.removeAll(c);
64 | if (result) {
65 | hashset.removeAll(c);
66 | }
67 | return result;
68 | }
69 |
70 | @Override
71 | public boolean retainAll(Collection> c) {
72 | boolean result = arraylist.retainAll(c);
73 | if (result) {
74 | hashset.retainAll(c);
75 | }
76 | return result;
77 | }
78 |
79 | @Override
80 | public void clear() {
81 | hashset.clear();
82 | arraylist.clear();
83 | }
84 |
85 | @Override
86 | public boolean remove(Object o) {
87 | arraylist.remove(o);
88 | hashset.remove(o);
89 | return true;
90 | }
91 |
92 | @Override
93 | public E get(int index) {
94 | return arraylist.get(index);
95 | }
96 |
97 | @Override
98 | public E remove(int index) {
99 | E e = arraylist.remove(index);
100 | hashset.remove(e);
101 | return e;
102 | }
103 |
104 | @Override
105 | public int indexOf(Object o) {
106 | return arraylist.indexOf(o);
107 | }
108 |
109 | @Override
110 | public int lastIndexOf(Object o) {
111 | return arraylist.lastIndexOf(o);
112 | }
113 |
114 | @Override
115 | public E set(int index, E element) {
116 | E result = arraylist.set(index, element);
117 | hashset.add(element);
118 | return result;
119 | }
120 |
121 | @Override
122 | public void add(int index, E element) {
123 | arraylist.add(index, element);
124 | hashset.add(element);
125 | }
126 |
127 | @Override
128 | public boolean addAll(int index, Collection extends E> c) {
129 | boolean result = arraylist.addAll(index, c);
130 | if (result) {
131 | hashset.addAll(c);
132 | }
133 | return result;
134 | }
135 |
136 | @Override
137 | public Iterator iterator() {
138 | return listIterator();
139 | }
140 |
141 | @Override
142 | public ListIterator listIterator() {
143 | return listIterator(0);
144 | }
145 |
146 | @Override
147 | public ListIterator listIterator(int index) {
148 | return new ListIteratorImpl(this, index);
149 | }
150 |
151 | @Override
152 | public List subList(int fromIndex, int toIndex) {
153 | throw new UnsupportedOperationException();
154 | }
155 |
156 | protected static class ListIteratorImpl implements ListIterator {
157 |
158 | protected final HashSetBackedArrayList list;
159 | protected int nextIndex;
160 | protected int lastRet;
161 |
162 | public ListIteratorImpl(HashSetBackedArrayList list, int nextIndex) {
163 | this.list = list;
164 | this.nextIndex = nextIndex;
165 | }
166 |
167 | @Override
168 | public boolean hasNext() {
169 | return nextIndex < list.size();
170 | }
171 |
172 | @Override
173 | public boolean hasPrevious() {
174 | return nextIndex != 0;
175 | }
176 |
177 | @Override
178 | public int nextIndex() {
179 | return nextIndex;
180 | }
181 |
182 | @Override
183 | public int previousIndex() {
184 | return nextIndex - 1;
185 | }
186 |
187 | @Override
188 | public E next() {
189 | if (!hasNext()) {
190 | throw new NoSuchElementException();
191 | }
192 | return list.get(lastRet = nextIndex++);
193 | }
194 |
195 | @Override
196 | public E previous() {
197 | if (!hasPrevious()) {
198 | throw new NoSuchElementException();
199 | }
200 | return list.get(lastRet = --nextIndex);
201 | }
202 |
203 | @Override
204 | public void remove() {
205 | if (lastRet < 0) {
206 | throw new IllegalStateException();
207 | }
208 | list.remove(nextIndex = lastRet);
209 | lastRet = -1;
210 | }
211 |
212 | @Override
213 | public void set(E e) {
214 | if (lastRet < 0) {
215 | throw new IllegalStateException();
216 | }
217 | list.set(lastRet, e);
218 | }
219 |
220 | @Override
221 | public void add(E e) {
222 | list.set(nextIndex++, e);
223 | lastRet = -1;
224 | }
225 |
226 | }
227 |
228 | }
229 |
--------------------------------------------------------------------------------
/src/tickoptimizer/utils/collections/HashSetFakeListImpl.java:
--------------------------------------------------------------------------------
1 | package tickoptimizer.utils.collections;
2 |
3 | import java.util.Collection;
4 | import java.util.HashSet;
5 | import java.util.List;
6 | import java.util.ListIterator;
7 |
8 | public class HashSetFakeListImpl extends HashSet implements List {
9 |
10 | private static final long serialVersionUID = -3747430484414343238L;
11 |
12 | @Override
13 | public boolean addAll(int index, Collection extends E> c) {
14 | throw new UnsupportedOperationException();
15 | }
16 |
17 | @Override
18 | public E get(int index) {
19 | throw new UnsupportedOperationException();
20 | }
21 |
22 | @Override
23 | public E set(int index, E element) {
24 | throw new UnsupportedOperationException();
25 | }
26 |
27 | @Override
28 | public void add(int index, E element) {
29 | throw new UnsupportedOperationException();
30 | }
31 |
32 | @Override
33 | public E remove(int index) {
34 | throw new UnsupportedOperationException();
35 | }
36 |
37 | @Override
38 | public int indexOf(Object o) {
39 | throw new UnsupportedOperationException();
40 | }
41 |
42 | @Override
43 | public int lastIndexOf(Object o) {
44 | throw new UnsupportedOperationException();
45 | }
46 |
47 | @Override
48 | public ListIterator listIterator() {
49 | throw new UnsupportedOperationException();
50 | }
51 |
52 | @Override
53 | public ListIterator listIterator(int index) {
54 | throw new UnsupportedOperationException();
55 | }
56 |
57 | @Override
58 | public List subList(int fromIndex, int toIndex) {
59 | throw new UnsupportedOperationException();
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/src/tickoptimizer/world/OptimizedNavigationListener.java:
--------------------------------------------------------------------------------
1 | package tickoptimizer.world;
2 |
3 | import java.util.Map.Entry;
4 |
5 | import org.bukkit.craftbukkit.libs.it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
6 |
7 | import net.minecraft.server.v1_12_R1.BlockPosition;
8 | import net.minecraft.server.v1_12_R1.Entity;
9 | import net.minecraft.server.v1_12_R1.EntityInsentient;
10 | import net.minecraft.server.v1_12_R1.IBlockData;
11 | import net.minecraft.server.v1_12_R1.NavigationAbstract;
12 | import net.minecraft.server.v1_12_R1.NavigationListener;
13 | import net.minecraft.server.v1_12_R1.PathEntity;
14 | import net.minecraft.server.v1_12_R1.PathPoint;
15 | import net.minecraft.server.v1_12_R1.World;
16 |
17 | public class OptimizedNavigationListener extends NavigationListener {
18 |
19 | private final Object2ObjectOpenHashMap navigators = new Object2ObjectOpenHashMap(1000);
20 |
21 | @Override
22 | public void a(final World world, final BlockPosition blockPosition, final IBlockData blockData, final IBlockData blockData2, final int n) {
23 | if (!a(world, blockPosition, blockData, blockData2)) {
24 | return;
25 | }
26 | for (Entry entry : navigators.entrySet()) {
27 | NavigationAbstract navigation = entry.getValue();
28 | if (!navigation.j()) {
29 | PathEntity pathentity = navigation.l();
30 | if (pathentity != null && !pathentity.b()) {
31 | if (pathentity.d() != 0) {
32 | PathPoint pathpoint = pathentity.c();
33 | EntityInsentient insentient = entry.getKey();
34 | if (
35 | blockPosition.distanceSquared(
36 | (pathpoint.a + insentient.locX) / 2.0,
37 | (pathpoint.b + insentient.locY) / 2.0,
38 | (pathpoint.c + insentient.locZ) / 2.0
39 | ) < (pathentity.d() - pathentity.e()) * (pathentity.d() - pathentity.e())
40 | ) {
41 | navigation.k();
42 | }
43 | }
44 | }
45 | }
46 | }
47 | }
48 |
49 | @Override
50 | public void a(Entity entity) {
51 | if (entity instanceof EntityInsentient) {
52 | EntityInsentient insentient = (EntityInsentient) entity;
53 | NavigationAbstract navigation = insentient.getNavigation();
54 | if (navigation != null) {
55 | navigators.put(insentient, navigation);
56 | }
57 | }
58 | }
59 |
60 | @Override
61 | public void b(Entity entity) {
62 | navigators.remove(entity);
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/src/tickoptimizer/world/WorldInjector.java:
--------------------------------------------------------------------------------
1 | package tickoptimizer.world;
2 |
3 | import java.lang.invoke.MethodHandle;
4 |
5 | import net.minecraft.server.v1_12_R1.EntityPlayer;
6 | import net.minecraft.server.v1_12_R1.PlayerChunkMap;
7 | import net.minecraft.server.v1_12_R1.TileEntity;
8 | import net.minecraft.server.v1_12_R1.World;
9 | import net.minecraft.server.v1_12_R1.WorldServer;
10 |
11 | import org.bukkit.Bukkit;
12 | import org.bukkit.craftbukkit.v1_12_R1.CraftWorld;
13 |
14 | import tickoptimizer.utils.Utils;
15 | import tickoptimizer.utils.collections.HashSetBackedArrayList;
16 | import tickoptimizer.utils.collections.HashSetFakeListImpl;
17 |
18 | public class WorldInjector {
19 |
20 | private final static MethodHandle tileEntityListTickFieldSetter = Utils.getFieldSetter(World.class, "tileEntityListTick", HashSetBackedArrayList.class);
21 | private final static MethodHandle managedPlayersPlayersFieldSetter = Utils.getFieldSetter(PlayerChunkMap.class, "managedPlayers", HashSetFakeListImpl.class);
22 | private final static MethodHandle navigationListener = Utils.getFieldSetter(World.class, "t", OptimizedNavigationListener.class);
23 |
24 | public static void inject(org.bukkit.World world) {
25 | try {
26 | WorldServer nmsWorldServer = ((CraftWorld) world).getHandle();
27 | World nmsWorld = nmsWorldServer;
28 | tileEntityListTickFieldSetter.invoke(nmsWorld, new HashSetBackedArrayList());
29 | navigationListener.invokeExact(nmsWorld, new OptimizedNavigationListener());
30 | PlayerChunkMap chunkmap = nmsWorldServer.getPlayerChunkMap();
31 | managedPlayersPlayersFieldSetter.invokeExact(chunkmap, new HashSetFakeListImpl());
32 | } catch (Throwable t) {
33 | t.printStackTrace();
34 | Bukkit.shutdown();
35 | }
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/tickoptimizer/world/block/InjectTEBlockBeacon.java:
--------------------------------------------------------------------------------
1 | package tickoptimizer.world.block;
2 |
3 | import tickoptimizer.world.tileentity.OptimizedTileEntityBeacon;
4 | import net.minecraft.server.v1_12_R1.BlockBeacon;
5 | import net.minecraft.server.v1_12_R1.TileEntity;
6 | import net.minecraft.server.v1_12_R1.World;
7 |
8 | public class InjectTEBlockBeacon extends BlockBeacon {
9 |
10 | public InjectTEBlockBeacon() {
11 | c("beacon");
12 | a(1.0F);
13 | }
14 |
15 | @Override
16 | public TileEntity a(final World world, final int n) {
17 | return new OptimizedTileEntityBeacon();
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/tickoptimizer/world/tileentity/OptimizedTileEntityBeacon.java:
--------------------------------------------------------------------------------
1 | package tickoptimizer.world.tileentity;
2 |
3 | import java.util.List;
4 |
5 | import net.minecraft.server.v1_12_R1.AxisAlignedBB;
6 | import net.minecraft.server.v1_12_R1.Block;
7 | import net.minecraft.server.v1_12_R1.BlockPosition;
8 | import net.minecraft.server.v1_12_R1.Blocks;
9 | import net.minecraft.server.v1_12_R1.CriterionTriggers;
10 | import net.minecraft.server.v1_12_R1.EntityHuman;
11 | import net.minecraft.server.v1_12_R1.EntityPlayer;
12 | import net.minecraft.server.v1_12_R1.MobEffect;
13 | import net.minecraft.server.v1_12_R1.MobEffectList;
14 | import net.minecraft.server.v1_12_R1.TileEntityBeacon;
15 |
16 | public class OptimizedTileEntityBeacon extends TileEntityBeacon {
17 |
18 | @Override
19 | public void n() {
20 | addEffects();
21 | checkStructure();
22 | }
23 |
24 | @SuppressWarnings("unchecked")
25 | private void addEffects() {
26 | if (this.levels > 0 && this.primaryEffect != null) {
27 | byte amplifier = 0;
28 | if (this.levels >= 4 && this.primaryEffect == this.secondaryEffect) {
29 | amplifier = 1;
30 | }
31 | final int duration = (9 + this.levels * 2) * 20;
32 | final List list = getHumansInRange();
33 | applyEffect(list, this.primaryEffect, duration, amplifier);
34 | if (this.levels >= 4 && this.primaryEffect != this.secondaryEffect && this.secondaryEffect != null) {
35 | applyEffect(list, this.secondaryEffect, duration, 0);
36 | }
37 | }
38 | }
39 |
40 | private void applyEffect(final List list, final MobEffectList effects, final int duration, final int amplifier) {
41 | for (final EntityHuman entityhuman : list) {
42 | entityhuman.addEffect(new MobEffect(effects, duration, amplifier, true, true));
43 | }
44 | }
45 |
46 | private void checkStructure() {
47 | int prevLevels = this.levels;
48 | this.levels = 0;
49 | final int beaconX = this.position.getX();
50 | final int beaconY = this.position.getY();
51 | final int beaconZ = this.position.getZ();
52 | if (this.world.getHighestBlockYAt(this.position).getY() > beaconY) {
53 | return;
54 | }
55 | for (int level = 1; level <= 4; level++) {
56 | final int y = beaconY - level;
57 | if (y < 0) {
58 | break;
59 | }
60 | for (int x = beaconX - level; x <= beaconX + level; ++x) {
61 | for (int z = beaconZ - level; z <= beaconZ + level; ++z) {
62 | if (!isValidBlock(new BlockPosition(x, y, z))) {
63 | return;
64 | }
65 | }
66 | }
67 | this.levels++;
68 | }
69 | if (prevLevels < this.levels) {
70 | for (final EntityPlayer entityplayer : this.world.a(EntityPlayer.class, new AxisAlignedBB(beaconX, beaconY, beaconZ, beaconX, beaconY - 4, beaconZ).grow(10.0, 5.0, 10.0))) {
71 | CriterionTriggers.k.a(entityplayer, this);
72 | }
73 | }
74 | }
75 |
76 | private boolean isValidBlock(BlockPosition blockpos) {
77 | if (this.world.isLoaded(blockpos)) {
78 | Block block = this.world.getType(blockpos).getBlock();
79 | if (block == Blocks.EMERALD_BLOCK || block == Blocks.GOLD_BLOCK || block == Blocks.DIAMOND_BLOCK || block == Blocks.IRON_BLOCK) {
80 | return true;
81 | }
82 | }
83 | return false;
84 | }
85 |
86 | }
87 |
--------------------------------------------------------------------------------