diff --git a/sources/src/main/java/net/minecraft/server/Entity.java b/sources/src/main/java/net/minecraft/server/Entity.java index 4adb62ed3..a0802c4f7 100644 --- a/sources/src/main/java/net/minecraft/server/Entity.java +++ b/sources/src/main/java/net/minecraft/server/Entity.java @@ -1478,6 +1478,7 @@ public boolean isCollidable() { return false; } + public void runKillTrigger(Entity entity, int kills, DamageSource damageSource) { this.a(entity, kills, damageSource); } // Paper - OBFHELPER public void a(Entity entity, int i, DamageSource damagesource) { if (entity instanceof EntityPlayer) { CriterionTriggers.c.a((EntityPlayer) entity, this, damagesource); @@ -2274,6 +2275,7 @@ public void onLightningStrike(EntityLightning entitylightning) { } + public void onKill(EntityLiving entityLiving) { this.b(entityLiving); } // Paper - OBFHELPER public void b(EntityLiving entityliving) {} protected boolean i(double d0, double d1, double d2) { @@ -2972,6 +2974,7 @@ public EnumPistonReaction getPushReaction() { return EnumPistonReaction.NORMAL; } + public SoundCategory getDeathSoundCategory() { return bK();} // Paper - OBFHELPER public SoundCategory bK() { return SoundCategory.NEUTRAL; } diff --git a/sources/src/main/java/net/minecraft/server/EntityPlayer.java b/sources/src/main/java/net/minecraft/server/EntityPlayer.java index 5817288ca..867b9e403 100644 --- a/sources/src/main/java/net/minecraft/server/EntityPlayer.java +++ b/sources/src/main/java/net/minecraft/server/EntityPlayer.java @@ -84,6 +84,10 @@ public void setViewDistance(int viewDistance) { } // Paper end private int containerUpdateDelay; // Paper + // Paper start - cancellable death event + public boolean queueHealthUpdatePacket = false; + public net.minecraft.server.PacketPlayOutUpdateHealth queuedHealthUpdatePacket; + // Paper end // CraftBukkit start public String displayName; @@ -441,9 +445,10 @@ private void a(IScoreboardCriteria iscoreboardcriteria, int i) { public void die(DamageSource damagesource) { boolean flag = this.world.getGameRules().getBoolean("showDeathMessages"); - this.playerConnection.sendPacket(new PacketPlayOutCombatEvent(this.getCombatTracker(), PacketPlayOutCombatEvent.EnumCombatEventType.ENTITY_DIED, flag)); + //this.playerConnection.sendPacket(new PacketPlayOutCombatEvent(this.getCombatTracker(), PacketPlayOutCombatEvent.EnumCombatEventType.ENTITY_DIED, flag)); // Paper - moved down for cancellable death event // CraftBukkit start - fire PlayerDeathEvent if (this.dead) { + this.playerConnection.sendPacket(new PacketPlayOutCombatEvent(this.getCombatTracker(), PacketPlayOutCombatEvent.EnumCombatEventType.ENTITY_DIED, flag)); // Paper - moved down for cancellable death event return; } java.util.List loot = new java.util.ArrayList(this.inventory.getSize()); @@ -461,6 +466,16 @@ public void die(DamageSource damagesource) { String deathmessage = chatmessage.toPlainText(); org.bukkit.event.entity.PlayerDeathEvent event = CraftEventFactory.callPlayerDeathEvent(this, loot, deathmessage, keepInventory); + // Paper start - cancellable death event + if (event.isCancelled()) { + // make compatible with plugins that might have already set the health in an event listener + if (this.getHealth() <= 0) { + this.setHealth((float) event.getReviveHealth()); + } + return; + } + this.playerConnection.sendPacket(new PacketPlayOutCombatEvent(this.getCombatTracker(), PacketPlayOutCombatEvent.EnumCombatEventType.ENTITY_DIED, flag)); + // Paper end String deathMessage = event.getDeathMessage(); @@ -614,7 +629,17 @@ public boolean damageEntity(DamageSource damagesource, float f) { } } - return super.damageEntity(damagesource, f); + // Paper start - cancellable death events + //return super.damageEntity(damagesource, f); + this.queueHealthUpdatePacket = true; + boolean damaged = super.damageEntity(damagesource, f); + this.queueHealthUpdatePacket = false; + if (this.queuedHealthUpdatePacket != null) { + this.playerConnection.sendPacket(this.queuedHealthUpdatePacket); + this.queuedHealthUpdatePacket = null; + } + return damaged; + // Paper end } } } diff --git a/sources/src/main/java/net/minecraft/server/PlayerConnection.java b/sources/src/main/java/net/minecraft/server/PlayerConnection.java index 1cca52638..9ec316dae 100644 --- a/sources/src/main/java/net/minecraft/server/PlayerConnection.java +++ b/sources/src/main/java/net/minecraft/server/PlayerConnection.java @@ -1,7 +1,9 @@ package net.minecraft.server; +import com.google.common.collect.Lists; import com.google.common.primitives.Doubles; import com.google.common.primitives.Floats; +import com.google.common.util.concurrent.Futures; import io.akarin.api.internal.Akari; import io.akarin.server.core.AkarinGlobalConfig; @@ -10,6 +12,8 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.Iterator; +import java.util.List; import java.util.Set; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; @@ -382,10 +386,13 @@ public void a(PacketPlayInVehicleMove packetplayinvehiclemove) { } entity.setLocation(d3, d4, d5, f, f1); + Location curPos = getPlayer().getLocation(); // Paper + player.setLocation(d3, d4, d5, f, f1); // Paper boolean flag2 = worldserver.getCubes(entity, entity.getBoundingBox().shrink(0.0625D)).isEmpty(); if (flag && (flag1 || !flag2)) { entity.setLocation(d0, d1, d2, f, f1); + player.setLocation(d0, d1, d2, f, f1); // Paper this.networkManager.sendPacket(new PacketPlayOutVehicleMove(entity)); return; } @@ -395,7 +402,7 @@ public void a(PacketPlayInVehicleMove packetplayinvehiclemove) { // Spigot Start if ( !hasMoved ) { - Location curPos = player.getLocation(); + //Location curPos = player.getLocation(); // Paper - move up lastPosX = curPos.getX(); lastPosY = curPos.getY(); lastPosZ = curPos.getZ(); @@ -2310,32 +2317,26 @@ public void a(PacketPlayInTabComplete packet) { buffer, isCommand, blockpos != null ? MCUtil.toLocation(player.world, blockpos) : null); event.callEvent(); completions = event.isCancelled() ? com.google.common.collect.ImmutableList.of() : event.getCompletions(); - if (event.isCancelled() || event.isHandled()) { - // Still fire sync event with the provided completions, if someone is listening - if (!event.isCancelled() && org.bukkit.event.server.TabCompleteEvent.getHandlerList().getRegisteredListeners().length > 0) { - java.util.List finalCompletions = completions; - Waitable> syncCompletions = new Waitable>() { - @Override - protected java.util.List evaluate() { - org.bukkit.event.server.TabCompleteEvent syncEvent = new org.bukkit.event.server.TabCompleteEvent(PlayerConnection.this.getPlayer(), buffer, finalCompletions, isCommand, blockpos != null ? MCUtil.toLocation(player.world, blockpos) : null); - return syncEvent.callEvent() ? syncEvent.getCompletions() : com.google.common.collect.ImmutableList.of(); - } - }; - server.getServer().processQueue.add(syncCompletions); - try { - completions = syncCompletions.get(); - } catch (InterruptedException | ExecutionException e1) { - e1.printStackTrace(); + if (!event.isHandled()) { + // If the event isn't handled, we can assume that we have no completions, and so we'll ask the server + + Waitable> syncCompletions = new Waitable>() { + @Override + protected java.util.List evaluate() { + return minecraftServer.tabCompleteCommand(player, buffer, blockpos, isCommand); } + }; + server.getServer().processQueue.add(syncCompletions); + try { + completions = syncCompletions.get(); + } catch (InterruptedException | ExecutionException e1) { + e1.printStackTrace(); } this.player.playerConnection.sendPacket(new PacketPlayOutTabComplete(completions.toArray(new String[completions.size()]))); - return; + } else if (!event.isCancelled()) { + this.player.playerConnection.sendPacket(new PacketPlayOutTabComplete(completions.toArray(new String[completions.size()]))); } - minecraftServer.postToMainThread(() -> { - java.util.List syncCompletions = this.minecraftServer.tabCompleteCommand(this.player, buffer, blockpos, isCommand); - this.player.playerConnection.sendPacket(new PacketPlayOutTabComplete(syncCompletions.toArray(new String[syncCompletions.size()]))); - }); // Paper end } @@ -2377,7 +2378,6 @@ public void a(PacketPlayInCustomPayload packetplayincustompayload) { } if (itemstack.getItem() == Items.WRITABLE_BOOK && itemstack.getItem() == itemstack1.getItem()) { - itemstack1 = new ItemStack(Items.WRITABLE_BOOK); // CraftBukkit itemstack1.a("pages", (NBTBase) itemstack.getTag().getList("pages", 8)); CraftEventFactory.handleEditBookEvent(player, itemstack1); // CraftBukkit } diff --git a/sources/src/main/java/net/minecraft/server/RegionFileCache.java b/sources/src/main/java/net/minecraft/server/RegionFileCache.java deleted file mode 100644 index 0c061fdd0..000000000 --- a/sources/src/main/java/net/minecraft/server/RegionFileCache.java +++ /dev/null @@ -1,127 +0,0 @@ -package net.minecraft.server; - -import com.destroystokyo.paper.exception.ServerInternalException; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.IOException; -import java.util.Iterator; -import java.util.Map; -import com.destroystokyo.paper.PaperConfig; // Paper -import java.util.LinkedHashMap; // Paper - -/** - * Akarin Changes Note - * 1) Removes unneed synchronization (performance) - */ -public class RegionFileCache { - - public static final Map a = new LinkedHashMap(PaperConfig.regionFileCacheSize, 0.75f, true); // Spigot - private -> public, Paper - HashMap -> LinkedHashMap - - public static synchronized RegionFile a(File file, int i, int j) { - File file1 = new File(file, "region"); - File file2 = new File(file1, "r." + (i >> 5) + "." + (j >> 5) + ".mca"); - RegionFile regionfile = (RegionFile) RegionFileCache.a.get(file2); - - if (regionfile != null) { - return regionfile; - } else { - if (!file1.exists()) { - file1.mkdirs(); - } - - if (RegionFileCache.a.size() >= PaperConfig.regionFileCacheSize) { // Paper - trimCache(); // Paper - } - - RegionFile regionfile1 = new RegionFile(file2); - - RegionFileCache.a.put(file2, regionfile1); - return regionfile1; - } - } - - public static synchronized RegionFile b(File file, int i, int j) { - File file1 = new File(file, "region"); - File file2 = new File(file1, "r." + (i >> 5) + "." + (j >> 5) + ".mca"); - RegionFile regionfile = (RegionFile) RegionFileCache.a.get(file2); - - if (regionfile != null) { - return regionfile; - } else if (file1.exists() && file2.exists()) { - if (RegionFileCache.a.size() >= 256) { - a(); - } - - RegionFile regionfile1 = new RegionFile(file2); - - RegionFileCache.a.put(file2, regionfile1); - return regionfile1; - } else { - return null; - } - } - - // Paper Start - private static synchronized void trimCache() { - Iterator> itr = RegionFileCache.a.entrySet().iterator(); - int count = RegionFileCache.a.size() - PaperConfig.regionFileCacheSize; - while (count-- >= 0 && itr.hasNext()) { - try { - itr.next().getValue().c(); - } catch (IOException ioexception) { - ioexception.printStackTrace(); - ServerInternalException.reportInternalException(ioexception); - } - itr.remove(); - } - } - // Paper End - - public static synchronized void a() { - Iterator iterator = RegionFileCache.a.values().iterator(); - - while (iterator.hasNext()) { - RegionFile regionfile = (RegionFile) iterator.next(); - - try { - if (regionfile != null) { - regionfile.c(); - } - } catch (IOException ioexception) { - ioexception.printStackTrace(); - ServerInternalException.reportInternalException(ioexception); // Paper - } - } - - RegionFileCache.a.clear(); - } - - // CraftBukkit start - call sites hoisted for synchronization - public static /*synchronized*/ NBTTagCompound d(File file, int i, int j) throws IOException { // Akarin - 1.13 backport - remove synchronization // OBFHELPER: read - RegionFile regionfile = a(file, i, j); - - DataInputStream datainputstream = regionfile.a(i & 31, j & 31); - - if (datainputstream == null) { - return null; - } - - return NBTCompressedStreamTools.a(datainputstream); - } - - public static /*synchronized*/ void e(File file, int i, int j, NBTTagCompound nbttagcompound) throws IOException { // Akarin - 1.13 backport - remove synchronization // OBFHELPER: write - RegionFile regionfile = a(file, i, j); - - DataOutputStream dataoutputstream = regionfile.b(i & 31, j & 31); - NBTCompressedStreamTools.a(nbttagcompound, (java.io.DataOutput) dataoutputstream); - dataoutputstream.close(); - } - // CraftBukkit end - - public static /*synchronized*/ boolean chunkExists(File file, int i, int j) { // Akarin - 1.13 backport - remove synchronization - RegionFile regionfile = b(file, i, j); - - return regionfile != null ? regionfile.c(i & 31, j & 31) : false; - } -} diff --git a/sources/src/main/java/net/minecraft/server/World.java b/sources/src/main/java/net/minecraft/server/World.java index 06a081df3..1f675d363 100644 --- a/sources/src/main/java/net/minecraft/server/World.java +++ b/sources/src/main/java/net/minecraft/server/World.java @@ -1184,7 +1184,7 @@ else if (entity instanceof EntityExperienceOrb) { int i = MathHelper.floor(entity.locX / 16.0D); int j = MathHelper.floor(entity.locZ / 16.0D); - boolean flag = entity.attachedToPlayer; + boolean flag = true; // Paper - always load chunks // Paper start - Set origin location when the entity is being added to the world if (entity.origin == null) { @@ -1831,7 +1831,7 @@ public void entityJoinedWorld(Entity entity, boolean flag) { this.getChunkAt(entity.ab, entity.ad).a(entity, entity.ac); } - if (false && !entity.bD() && !this.isChunkLoaded(i, k, true)) { // Paper - Always send entities into a new chunk, never lose them + if (!entity.valid && !entity.bD() && !this.isChunkLoaded(i, k, true)) { // Paper - always load to new chunk if valid entity.aa = false; } else { this.getChunkAt(i, k).a(entity); diff --git a/sources/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/sources/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java index e215bfae6..9400de675 100644 --- a/sources/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/sources/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -1622,7 +1622,15 @@ public void updateScaledHealth() { } public void sendHealthUpdate() { - getHandle().playerConnection.sendPacket(new PacketPlayOutUpdateHealth(getScaledHealth(), getHandle().getFoodData().getFoodLevel(), getHandle().getFoodData().getSaturationLevel())); + // Paper start - cancellable death event + //getHandle().playerConnection.sendPacket(new PacketPlayOutUpdateHealth(getScaledHealth(), getHandle().getFoodData().getFoodLevel(), getHandle().getFoodData().getSaturationLevel())); + PacketPlayOutUpdateHealth packet = new PacketPlayOutUpdateHealth(getScaledHealth(), getHandle().getFoodData().getFoodLevel(), getHandle().getFoodData().getSaturationLevel()); + if (this.getHandle().queueHealthUpdatePacket) { + this.getHandle().queuedHealthUpdatePacket = packet; + } else { + this.getHandle().playerConnection.sendPacket(packet); + } + // Paper end } public void injectScaledMaxHealth(Collection collection, boolean force) { diff --git a/sources/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/sources/src/main/java/org/bukkit/plugin/SimplePluginManager.java new file mode 100644 index 000000000..3ae5654c7 --- /dev/null +++ b/sources/src/main/java/org/bukkit/plugin/SimplePluginManager.java @@ -0,0 +1,791 @@ +package org.bukkit.plugin; + +import java.io.File; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.destroystokyo.paper.event.server.ServerExceptionEvent; +import com.destroystokyo.paper.exception.ServerEventException; +import com.destroystokyo.paper.exception.ServerPluginEnableDisableException; +import org.apache.commons.lang.Validate; +import org.bukkit.Server; +import org.bukkit.command.Command; +import org.bukkit.command.PluginCommandYamlParser; +import org.bukkit.command.SimpleCommandMap; +import org.bukkit.event.Event; +import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.permissions.Permissible; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionDefault; +import org.bukkit.util.FileUtil; + +import com.google.common.collect.ImmutableSet; + +/** + * Akarin Changes Note + * 1) Reverts Paper's changes to ensure event order (safety issue) + */ +/** + * Handles all plugin management from the Server + */ +public final class SimplePluginManager implements PluginManager { + private final Server server; + private final Map fileAssociations = new HashMap(); + private final List plugins = new ArrayList(); + private final Map lookupNames = new HashMap(); + private File updateDirectory; + private final SimpleCommandMap commandMap; + private final Map permissions = new HashMap(); + private final Map> defaultPerms = new LinkedHashMap>(); + private final Map> permSubs = new HashMap>(); + private final Map> defSubs = new HashMap>(); + private boolean useTimings = false; + + public SimplePluginManager(Server instance, SimpleCommandMap commandMap) { + server = instance; + this.commandMap = commandMap; + + defaultPerms.put(true, new HashSet()); + defaultPerms.put(false, new HashSet()); + } + + /** + * Registers the specified plugin loader + * + * @param loader Class name of the PluginLoader to register + * @throws IllegalArgumentException Thrown when the given Class is not a + * valid PluginLoader + */ + public void registerInterface(Class loader) throws IllegalArgumentException { + PluginLoader instance; + + if (PluginLoader.class.isAssignableFrom(loader)) { + Constructor constructor; + + try { + constructor = loader.getConstructor(Server.class); + instance = constructor.newInstance(server); + } catch (NoSuchMethodException ex) { + String className = loader.getName(); + + throw new IllegalArgumentException(String.format("Class %s does not have a public %s(Server) constructor", className, className), ex); + } catch (Exception ex) { + throw new IllegalArgumentException(String.format("Unexpected exception %s while attempting to construct a new instance of %s", ex.getClass().getName(), loader.getName()), ex); + } + } else { + throw new IllegalArgumentException(String.format("Class %s does not implement interface PluginLoader", loader.getName())); + } + + Pattern[] patterns = instance.getPluginFileFilters(); + + synchronized (this) { + for (Pattern pattern : patterns) { + fileAssociations.put(pattern, instance); + } + } + } + + /** + * Loads the plugins contained within the specified directory + * + * @param directory Directory to check for plugins + * @return A list of all plugins loaded + */ + public Plugin[] loadPlugins(File directory) { + Validate.notNull(directory, "Directory cannot be null"); + Validate.isTrue(directory.isDirectory(), "Directory must be a directory"); + + List result = new ArrayList(); + Set filters = fileAssociations.keySet(); + + if (!(server.getUpdateFolder().equals(""))) { + updateDirectory = new File(directory, server.getUpdateFolder()); + } + + Map plugins = new HashMap(); + Set loadedPlugins = new HashSet(); + Map> dependencies = new HashMap>(); + Map> softDependencies = new HashMap>(); + + // This is where it figures out all possible plugins + for (File file : directory.listFiles()) { + PluginLoader loader = null; + for (Pattern filter : filters) { + Matcher match = filter.matcher(file.getName()); + if (match.find()) { + loader = fileAssociations.get(filter); + } + } + + if (loader == null) continue; + + PluginDescriptionFile description = null; + try { + description = loader.getPluginDescription(file); + String name = description.getName(); + if (name.equalsIgnoreCase("bukkit") || name.equalsIgnoreCase("minecraft") || name.equalsIgnoreCase("mojang")) { + server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "': Restricted Name"); + continue; + } else if (description.rawName.indexOf(' ') != -1) { + server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "': uses the space-character (0x20) in its name"); + continue; + } + } catch (InvalidDescriptionException ex) { + server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "'", ex); + continue; + } + + File replacedFile = plugins.put(description.getName(), file); + if (replacedFile != null) { + server.getLogger().severe(String.format( + "Ambiguous plugin name `%s' for files `%s' and `%s' in `%s'", + description.getName(), + file.getPath(), + replacedFile.getPath(), + directory.getPath() + )); + } + + Collection softDependencySet = description.getSoftDepend(); + if (softDependencySet != null && !softDependencySet.isEmpty()) { + if (softDependencies.containsKey(description.getName())) { + // Duplicates do not matter, they will be removed together if applicable + softDependencies.get(description.getName()).addAll(softDependencySet); + } else { + softDependencies.put(description.getName(), new LinkedList(softDependencySet)); + } + } + + Collection dependencySet = description.getDepend(); + if (dependencySet != null && !dependencySet.isEmpty()) { + dependencies.put(description.getName(), new LinkedList(dependencySet)); + } + + Collection loadBeforeSet = description.getLoadBefore(); + if (loadBeforeSet != null && !loadBeforeSet.isEmpty()) { + for (String loadBeforeTarget : loadBeforeSet) { + if (softDependencies.containsKey(loadBeforeTarget)) { + softDependencies.get(loadBeforeTarget).add(description.getName()); + } else { + // softDependencies is never iterated, so 'ghost' plugins aren't an issue + Collection shortSoftDependency = new LinkedList(); + shortSoftDependency.add(description.getName()); + softDependencies.put(loadBeforeTarget, shortSoftDependency); + } + } + } + } + + while (!plugins.isEmpty()) { + boolean missingDependency = true; + Iterator> pluginIterator = plugins.entrySet().iterator(); + + while (pluginIterator.hasNext()) { + Map.Entry entry = pluginIterator.next(); + String plugin = entry.getKey(); + + if (dependencies.containsKey(plugin)) { + Iterator dependencyIterator = dependencies.get(plugin).iterator(); + + while (dependencyIterator.hasNext()) { + String dependency = dependencyIterator.next(); + + // Dependency loaded + if (loadedPlugins.contains(dependency)) { + dependencyIterator.remove(); + + // We have a dependency not found + } else if (!plugins.containsKey(dependency)) { + missingDependency = false; + pluginIterator.remove(); + softDependencies.remove(plugin); + dependencies.remove(plugin); + + server.getLogger().log( + Level.SEVERE, + "Could not load '" + entry.getValue().getPath() + "' in folder '" + directory.getPath() + "'", + new UnknownDependencyException(dependency)); + break; + } + } + + if (dependencies.containsKey(plugin) && dependencies.get(plugin).isEmpty()) { + dependencies.remove(plugin); + } + } + if (softDependencies.containsKey(plugin)) { + Iterator softDependencyIterator = softDependencies.get(plugin).iterator(); + + while (softDependencyIterator.hasNext()) { + String softDependency = softDependencyIterator.next(); + + // Soft depend is no longer around + if (!plugins.containsKey(softDependency)) { + softDependencyIterator.remove(); + } + } + + if (softDependencies.get(plugin).isEmpty()) { + softDependencies.remove(plugin); + } + } + if (!(dependencies.containsKey(plugin) || softDependencies.containsKey(plugin)) && plugins.containsKey(plugin)) { + // We're clear to load, no more soft or hard dependencies left + File file = plugins.get(plugin); + pluginIterator.remove(); + missingDependency = false; + + try { + result.add(loadPlugin(file)); + loadedPlugins.add(plugin); + continue; + } catch (InvalidPluginException ex) { + server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "'", ex); + } + } + } + + if (missingDependency) { + // We now iterate over plugins until something loads + // This loop will ignore soft dependencies + pluginIterator = plugins.entrySet().iterator(); + + while (pluginIterator.hasNext()) { + Map.Entry entry = pluginIterator.next(); + String plugin = entry.getKey(); + + if (!dependencies.containsKey(plugin)) { + softDependencies.remove(plugin); + missingDependency = false; + File file = entry.getValue(); + pluginIterator.remove(); + + try { + result.add(loadPlugin(file)); + loadedPlugins.add(plugin); + break; + } catch (InvalidPluginException ex) { + server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "'", ex); + } + } + } + // We have no plugins left without a depend + if (missingDependency) { + softDependencies.clear(); + dependencies.clear(); + Iterator failedPluginIterator = plugins.values().iterator(); + + while (failedPluginIterator.hasNext()) { + File file = failedPluginIterator.next(); + failedPluginIterator.remove(); + server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "': circular dependency detected"); + } + } + } + } + + return result.toArray(new Plugin[result.size()]); + } + + /** + * Loads the plugin in the specified file + *

+ * File must be valid according to the current enabled Plugin interfaces + * + * @param file File containing the plugin to load + * @return The Plugin loaded, or null if it was invalid + * @throws InvalidPluginException Thrown when the specified file is not a + * valid plugin + * @throws UnknownDependencyException If a required dependency could not + * be found + */ + public synchronized Plugin loadPlugin(File file) throws InvalidPluginException, UnknownDependencyException { + Validate.notNull(file, "File cannot be null"); + + checkUpdate(file); + + Set filters = fileAssociations.keySet(); + Plugin result = null; + + for (Pattern filter : filters) { + String name = file.getName(); + Matcher match = filter.matcher(name); + + if (match.find()) { + PluginLoader loader = fileAssociations.get(filter); + + result = loader.loadPlugin(file); + } + } + + if (result != null) { + plugins.add(result); + lookupNames.put(result.getDescription().getName().toLowerCase(java.util.Locale.ENGLISH), result); // Spigot + } + + return result; + } + + private void checkUpdate(File file) { + if (updateDirectory == null || !updateDirectory.isDirectory()) { + return; + } + + File updateFile = new File(updateDirectory, file.getName()); + if (updateFile.isFile() && FileUtil.copy(updateFile, file)) { + updateFile.delete(); + } + } + + /** + * Checks if the given plugin is loaded and returns it when applicable + *

+ * Please note that the name of the plugin is case-sensitive + * + * @param name Name of the plugin to check + * @return Plugin if it exists, otherwise null + */ + public synchronized Plugin getPlugin(String name) { + return lookupNames.get(name.replace(' ', '_').toLowerCase(java.util.Locale.ENGLISH)); // Spigot + } + + public synchronized Plugin[] getPlugins() { + return plugins.toArray(new Plugin[plugins.size()]); + } + + /** + * Checks if the given plugin is enabled or not + *

+ * Please note that the name of the plugin is case-sensitive. + * + * @param name Name of the plugin to check + * @return true if the plugin is enabled, otherwise false + */ + public boolean isPluginEnabled(String name) { + Plugin plugin = getPlugin(name); + + return isPluginEnabled(plugin); + } + + /** + * Checks if the given plugin is enabled or not + * + * @param plugin Plugin to check + * @return true if the plugin is enabled, otherwise false + */ + public synchronized boolean isPluginEnabled(Plugin plugin) { // Paper - synchronize + if ((plugin != null) && (plugins.contains(plugin))) { + return plugin.isEnabled(); + } else { + return false; + } + } + + public synchronized void enablePlugin(final Plugin plugin) { // Paper - synchronize + if (!plugin.isEnabled()) { + List pluginCommands = PluginCommandYamlParser.parse(plugin); + + if (!pluginCommands.isEmpty()) { + commandMap.registerAll(plugin.getDescription().getName(), pluginCommands); + } + + try { + plugin.getPluginLoader().enablePlugin(plugin); + } catch (Throwable ex) { + handlePluginException("Error occurred (in the plugin loader) while enabling " + + plugin.getDescription().getFullName() + " (Is it up to date?)", ex, plugin); + } + + HandlerList.bakeAll(); + } + } + + // Paper start - close Classloader on disable + public void disablePlugins() { + disablePlugins(false); + } + + public void disablePlugins(boolean closeClassloaders) { + // Paper end - close Classloader on disable + Plugin[] plugins = getPlugins(); + for (int i = plugins.length - 1; i >= 0; i--) { + disablePlugin(plugins[i], closeClassloaders); // Paper - close Classloader on disable + } + } + + // Paper start - close Classloader on disable + public void disablePlugin(Plugin plugin) { + disablePlugin(plugin, false); + } + + public synchronized void disablePlugin(final Plugin plugin, boolean closeClassloader) { // Paper - synchronize + // Paper end - close Classloader on disable + if (plugin.isEnabled()) { + try { + plugin.getPluginLoader().disablePlugin(plugin, closeClassloader); // Paper - close Classloader on disable + } catch (Throwable ex) { + handlePluginException("Error occurred (in the plugin loader) while disabling " + + plugin.getDescription().getFullName() + " (Is it up to date?)", ex, plugin); // Paper + } + + try { + server.getScheduler().cancelTasks(plugin); + } catch (Throwable ex) { + handlePluginException("Error occurred (in the plugin loader) while cancelling tasks for " + + plugin.getDescription().getFullName() + " (Is it up to date?)", ex, plugin); // Paper + } + + try { + server.getServicesManager().unregisterAll(plugin); + } catch (Throwable ex) { + handlePluginException("Error occurred (in the plugin loader) while unregistering services for " + + plugin.getDescription().getFullName() + " (Is it up to date?)", ex, plugin); // Paper + } + + try { + HandlerList.unregisterAll(plugin); + } catch (Throwable ex) { + handlePluginException("Error occurred (in the plugin loader) while unregistering events for " + + plugin.getDescription().getFullName() + " (Is it up to date?)", ex, plugin); // Paper + } + + try { + server.getMessenger().unregisterIncomingPluginChannel(plugin); + server.getMessenger().unregisterOutgoingPluginChannel(plugin); + } catch (Throwable ex) { + handlePluginException("Error occurred (in the plugin loader) while unregistering plugin channels for " + + plugin.getDescription().getFullName() + " (Is it up to date?)", ex, plugin); // Paper + } + } + } + + // Paper start + private void handlePluginException(String msg, Throwable ex, Plugin plugin) { + server.getLogger().log(Level.SEVERE, msg, ex); + callEvent(new ServerExceptionEvent(new ServerPluginEnableDisableException(msg, ex, plugin))); + } + // Paper end + + public void clearPlugins() { + synchronized (this) { + disablePlugins(true); // Paper - close Classloader on disable + plugins.clear(); + lookupNames.clear(); + HandlerList.unregisterAll(); + fileAssociations.clear(); + permissions.clear(); + defaultPerms.get(true).clear(); + defaultPerms.get(false).clear(); + } + } + + /** + * Calls an event with the given details. + *

+ * This method only synchronizes when the event is not asynchronous. + * + * @param event Event details + */ + public void callEvent(Event event) { + if (event.isAsynchronous()) { + if (Thread.holdsLock(this)) { + throw new IllegalStateException(event.getEventName() + " cannot be triggered asynchronously from inside synchronized code."); + } + if (server.isPrimaryThread()) { + throw new IllegalStateException(event.getEventName() + " cannot be triggered asynchronously from primary server thread."); + } + fireEvent(event); + } else { + synchronized (this) { + fireEvent(event); + } + } + } + + private void fireEvent(Event event) { + HandlerList handlers = event.getHandlers(); + RegisteredListener[] listeners = handlers.getRegisteredListeners(); + + for (RegisteredListener registration : listeners) { + if (!registration.getPlugin().isEnabled()) { + continue; + } + + try { + registration.callEvent(event); + } catch (AuthorNagException ex) { + Plugin plugin = registration.getPlugin(); + + if (plugin.isNaggable()) { + plugin.setNaggable(false); + + server.getLogger().log(Level.SEVERE, String.format( + "Nag author(s): '%s' of '%s' about the following: %s", + plugin.getDescription().getAuthors(), + plugin.getDescription().getFullName(), + ex.getMessage() + )); + } + } catch (Throwable ex) { + // Paper start - error reporting + String msg = "Could not pass event " + event.getEventName() + " to " + registration.getPlugin().getDescription().getFullName(); + server.getLogger().log(Level.SEVERE, msg, ex); + if (!(event instanceof ServerExceptionEvent)) { // We don't want to cause an endless event loop + callEvent(new ServerExceptionEvent(new ServerEventException(msg, ex, registration.getPlugin(), registration.getListener(), event))); + } + // Paper end + } + } + } + + public void registerEvents(Listener listener, Plugin plugin) { + if (!plugin.isEnabled()) { + throw new IllegalPluginAccessException("Plugin attempted to register " + listener + " while not enabled"); + } + + for (Map.Entry, Set> entry : plugin.getPluginLoader().createRegisteredListeners(listener, plugin).entrySet()) { + getEventListeners(getRegistrationClass(entry.getKey())).registerAll(entry.getValue()); + } + + } + + public void registerEvent(Class event, Listener listener, EventPriority priority, EventExecutor executor, Plugin plugin) { + registerEvent(event, listener, priority, executor, plugin, false); + } + + /** + * Registers the given event to the specified listener using a directly + * passed EventExecutor + * + * @param event Event class to register + * @param listener PlayerListener to register + * @param priority Priority of this event + * @param executor EventExecutor to register + * @param plugin Plugin to register + * @param ignoreCancelled Do not call executor if event was already + * cancelled + */ + public void registerEvent(Class event, Listener listener, EventPriority priority, EventExecutor executor, Plugin plugin, boolean ignoreCancelled) { + Validate.notNull(listener, "Listener cannot be null"); + Validate.notNull(priority, "Priority cannot be null"); + Validate.notNull(executor, "Executor cannot be null"); + Validate.notNull(plugin, "Plugin cannot be null"); + + if (!plugin.isEnabled()) { + throw new IllegalPluginAccessException("Plugin attempted to register " + event + " while not enabled"); + } + + executor = new co.aikar.timings.TimedEventExecutor(executor, plugin, null, event); // Spigot + if (false) { // Spigot - RL handles useTimings check now + getEventListeners(event).register(new TimedRegisteredListener(listener, executor, priority, plugin, ignoreCancelled)); + } else { + getEventListeners(event).register(new RegisteredListener(listener, executor, priority, plugin, ignoreCancelled)); + } + } + + private HandlerList getEventListeners(Class type) { + try { + Method method = getRegistrationClass(type).getDeclaredMethod("getHandlerList"); + method.setAccessible(true); + return (HandlerList) method.invoke(null); + } catch (Exception e) { + throw new IllegalPluginAccessException(e.toString()); + } + } + + private Class getRegistrationClass(Class clazz) { + try { + clazz.getDeclaredMethod("getHandlerList"); + return clazz; + } catch (NoSuchMethodException e) { + if (clazz.getSuperclass() != null + && !clazz.getSuperclass().equals(Event.class) + && Event.class.isAssignableFrom(clazz.getSuperclass())) { + return getRegistrationClass(clazz.getSuperclass().asSubclass(Event.class)); + } else { + throw new IllegalPluginAccessException("Unable to find handler list for event " + clazz.getName() + ". Static getHandlerList method required!"); + } + } + } + + public Permission getPermission(String name) { + return permissions.get(name.toLowerCase(java.util.Locale.ENGLISH)); + } + + public void addPermission(Permission perm) { + addPermission(perm, true); + } + + @Deprecated + public void addPermission(Permission perm, boolean dirty) { + String name = perm.getName().toLowerCase(java.util.Locale.ENGLISH); + + if (permissions.containsKey(name)) { + throw new IllegalArgumentException("The permission " + name + " is already defined!"); + } + + permissions.put(name, perm); + calculatePermissionDefault(perm, dirty); + } + + public Set getDefaultPermissions(boolean op) { + return ImmutableSet.copyOf(defaultPerms.get(op)); + } + + public void removePermission(Permission perm) { + removePermission(perm.getName()); + } + + public void removePermission(String name) { + permissions.remove(name.toLowerCase(java.util.Locale.ENGLISH)); + } + + public void recalculatePermissionDefaults(Permission perm) { + if (perm != null && permissions.containsKey(perm.getName().toLowerCase(java.util.Locale.ENGLISH))) { + defaultPerms.get(true).remove(perm); + defaultPerms.get(false).remove(perm); + + calculatePermissionDefault(perm, true); + } + } + + private void calculatePermissionDefault(Permission perm, boolean dirty) { + if ((perm.getDefault() == PermissionDefault.OP) || (perm.getDefault() == PermissionDefault.TRUE)) { + defaultPerms.get(true).add(perm); + if (dirty) { + dirtyPermissibles(true); + } + } + if ((perm.getDefault() == PermissionDefault.NOT_OP) || (perm.getDefault() == PermissionDefault.TRUE)) { + defaultPerms.get(false).add(perm); + if (dirty) { + dirtyPermissibles(false); + } + } + } + + @Deprecated + public void dirtyPermissibles() { + dirtyPermissibles(true); + dirtyPermissibles(false); + } + + private void dirtyPermissibles(boolean op) { + Set permissibles = getDefaultPermSubscriptions(op); + + for (Permissible p : permissibles) { + p.recalculatePermissions(); + } + } + + public void subscribeToPermission(String permission, Permissible permissible) { + String name = permission.toLowerCase(java.util.Locale.ENGLISH); + Map map = permSubs.get(name); + + if (map == null) { + map = new WeakHashMap(); + permSubs.put(name, map); + } + + map.put(permissible, true); + } + + public void unsubscribeFromPermission(String permission, Permissible permissible) { + String name = permission.toLowerCase(java.util.Locale.ENGLISH); + Map map = permSubs.get(name); + + if (map != null) { + map.remove(permissible); + + if (map.isEmpty()) { + permSubs.remove(name); + } + } + } + + public Set getPermissionSubscriptions(String permission) { + String name = permission.toLowerCase(java.util.Locale.ENGLISH); + Map map = permSubs.get(name); + + if (map == null) { + return ImmutableSet.of(); + } else { + return ImmutableSet.copyOf(map.keySet()); + } + } + + public void subscribeToDefaultPerms(boolean op, Permissible permissible) { + Map map = defSubs.get(op); + + if (map == null) { + map = new WeakHashMap(); + defSubs.put(op, map); + } + + map.put(permissible, true); + } + + public void unsubscribeFromDefaultPerms(boolean op, Permissible permissible) { + Map map = defSubs.get(op); + + if (map != null) { + map.remove(permissible); + + if (map.isEmpty()) { + defSubs.remove(op); + } + } + } + + public Set getDefaultPermSubscriptions(boolean op) { + Map map = defSubs.get(op); + + if (map == null) { + return ImmutableSet.of(); + } else { + return ImmutableSet.copyOf(map.keySet()); + } + } + + public Set getPermissions() { + return new HashSet(permissions.values()); + } + + public boolean useTimings() { + return co.aikar.timings.Timings.isTimingsEnabled(); // Spigot + } + + /** + * Sets whether or not per event timing code should be used + * + * @param use True if per event timing code should be used + */ + public void useTimings(boolean use) { + co.aikar.timings.Timings.setTimingsEnabled(use); // Spigot + } + + // Paper start + public void clearPermissions() { + permissions.clear(); + defaultPerms.get(true).clear(); + defaultPerms.get(false).clear(); + } + // Paper end + +} diff --git a/work/Paper b/work/Paper index a4f2aa211..291642844 160000 --- a/work/Paper +++ b/work/Paper @@ -1 +1 @@ -Subproject commit a4f2aa2110c1d583cb1258055747a7d0d66f62ea +Subproject commit 29164284499cfff6ad4658c0522445f2f3b3be6c