├── .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 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 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 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 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 | --------------------------------------------------------------------------------