From fb4ed9362d0b5de5ac8a8ea534df485e6f3d8dfd Mon Sep 17 00:00:00 2001 From: Jesse Boyd Date: Wed, 6 Apr 2016 01:11:24 +1000 Subject: [PATCH] Various per player limits - max blocks - max checks - max fails - max entities - max blockstates Also add command limit Fix negative coord compression issue --- .../classes/production/forge_main/config.yml | 0 .../com/boydti/fawe/bukkit/WEListener.java | 7 +- .../bukkit/v0/BukkitEditSessionWrapper_0.java | 7 +- .../fawe/bukkit/v1_8/BukkitQueue_1_8.java | 3 + .../fawe/bukkit/v1_9/BukkitQueue_1_9.java | 3 + .../main/java/com/boydti/fawe/FaweCache.java | 9 + .../java/com/boydti/fawe/command/Reload.java | 3 +- .../com/boydti/fawe/command/Undolist.java | 16 + .../main/java/com/boydti/fawe/config/BBC.java | 1 + .../java/com/boydti/fawe/config/Settings.java | 47 +- .../fawe/object/EditSessionWrapper.java | 5 +- .../com/boydti/fawe/object/FaweLimit.java | 54 ++ .../com/boydti/fawe/object/FawePlayer.java | 6 +- .../com/boydti/fawe/object/HistoryExtent.java | 7 +- .../object/changeset/DiskStorageHistory.java | 14 +- .../changeset/MemoryOptimizedHistory.java | 16 +- .../object/extent/FastWorldEditExtent.java | 25 +- .../fawe/object/extent/ProcessedWEExtent.java | 74 +-- .../regions/general/PlotSquaredFeature.java | 6 +- .../java/com/boydti/fawe/util/WEManager.java | 1 - .../java/com/sk89q/worldedit/EditSession.java | 69 +-- .../extension/platform/CommandManager.java | 18 +- forge/.gradle/gradle.log | 89 --- .../com/boydti/fawe/forge/FaweSponge.java | 3 +- .../com/boydti/fawe/forge/SpongeMetrics.java | 518 ++++++++++++++++++ 25 files changed, 767 insertions(+), 234 deletions(-) delete mode 100644 build/classes/production/forge_main/config.yml create mode 100644 core/src/main/java/com/boydti/fawe/command/Undolist.java create mode 100644 core/src/main/java/com/boydti/fawe/object/FaweLimit.java delete mode 100644 forge/.gradle/gradle.log create mode 100644 forge/src/main/java/com/boydti/fawe/forge/SpongeMetrics.java diff --git a/build/classes/production/forge_main/config.yml b/build/classes/production/forge_main/config.yml deleted file mode 100644 index e69de29b..00000000 diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/WEListener.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/WEListener.java index c404ff26..fc4125e5 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/WEListener.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/WEListener.java @@ -3,6 +3,7 @@ package com.boydti.fawe.bukkit; import com.boydti.fawe.Fawe; import com.boydti.fawe.config.BBC; import com.boydti.fawe.config.Settings; +import com.boydti.fawe.object.FaweLimit; import com.boydti.fawe.object.FawePlayer; import com.boydti.fawe.object.RegionWrapper; import com.boydti.fawe.util.MainUtil; @@ -157,9 +158,9 @@ public class WEListener implements Listener { final String cmd = message.toLowerCase(); final boolean single = true; final String[] split = cmd.split(" "); - - final long maxVolume = Settings.WE_MAX_VOLUME; - final long maxIterations = Settings.WE_MAX_ITERATIONS; + FaweLimit limit = player.getLimit(); + final long maxVolume = limit.MAX_FAILS + limit.MAX_CHANGES; + final long maxIterations = limit.MAX_ITERATIONS; // if (player.hasPermission("fawe.bypass")) { // return true; // } diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitEditSessionWrapper_0.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitEditSessionWrapper_0.java index 4577ade3..4a98c616 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitEditSessionWrapper_0.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitEditSessionWrapper_0.java @@ -2,8 +2,10 @@ package com.boydti.fawe.bukkit.v0; import com.boydti.fawe.bukkit.logging.BlocksHubHook; import com.boydti.fawe.object.EditSessionWrapper; +import com.boydti.fawe.object.FaweLimit; import com.boydti.fawe.object.FawePlayer; import com.boydti.fawe.object.changeset.FaweChangeSet; +import com.boydti.fawe.util.FaweQueue; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.extent.Extent; @@ -20,13 +22,12 @@ public class BukkitEditSessionWrapper_0 extends EditSessionWrapper { } @Override - public Extent getHistoryExtent(final Extent parent, final FaweChangeSet set, final FawePlayer player) { + public Extent getHistoryExtent(String world, FaweLimit limit, Extent parent, FaweChangeSet set, FaweQueue queue, FawePlayer player) { if (this.hook != null) { // If we are doing logging, return a custom logging extent return this.hook.getLoggingExtent(parent, set, player); } // Otherwise return the normal history extent - return super.getHistoryExtent(parent, set, player); + return super.getHistoryExtent(world, limit, parent, set, queue, player); } - } diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_8/BukkitQueue_1_8.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_8/BukkitQueue_1_8.java index 874c45c5..a6c4cd02 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_8/BukkitQueue_1_8.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_8/BukkitQueue_1_8.java @@ -266,6 +266,9 @@ public class BukkitQueue_1_8 extends BukkitQueue_0 { } lcx = cx; lcz = cz; + if (!bukkitWorld.isChunkLoaded(cx, cz)) { + return 0; + } lc = methodGetHandleChunk.of(bukkitWorld.getChunkAt(cx, cz)).call(); } else if (cy == lcy) { return ls != null ? ls[FaweCache.CACHE_J[y][x & 15][z & 15]] : 0; diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_9/BukkitQueue_1_9.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_9/BukkitQueue_1_9.java index 32d0d836..bb511c4b 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_9/BukkitQueue_1_9.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_9/BukkitQueue_1_9.java @@ -242,6 +242,9 @@ public class BukkitQueue_1_9 extends BukkitQueue_0 { } lcx = cx; lcz = cz; + if (!bukkitWorld.isChunkLoaded(cx, cz)) { + return 0; + } lc = methodGetType.of(methodGetHandleChunk.of(bukkitWorld.getChunkAt(cx, cz)).call()); } int combined = (int) methodGetCombinedId.call(lc.call(x & 15, y, z & 15)); diff --git a/core/src/main/java/com/boydti/fawe/FaweCache.java b/core/src/main/java/com/boydti/fawe/FaweCache.java index be042e21..569b3272 100644 --- a/core/src/main/java/com/boydti/fawe/FaweCache.java +++ b/core/src/main/java/com/boydti/fawe/FaweCache.java @@ -1,6 +1,7 @@ package com.boydti.fawe; import com.boydti.fawe.object.PseudoRandom; +import com.sk89q.worldedit.blocks.BaseBlock; public class FaweCache { public final static short[][][] CACHE_I = new short[256][16][16]; @@ -13,6 +14,8 @@ public class FaweCache { public final static short[] CACHE_ID = new short[65535]; public final static byte[] CACHE_DATA = new byte[65535]; + public final static BaseBlock[] CACHE_BLOCK = new BaseBlock[Short.MAX_VALUE]; + // Faster than java random (since the game just needs to look random) public final static PseudoRandom RANDOM = new PseudoRandom(); @@ -36,6 +39,12 @@ public class FaweCache { CACHE_ID[i] = (short) j; CACHE_DATA[i] = (byte) k; } + + for (int i = 0; i < Short.MAX_VALUE; i++) { + int id = i >> 4; + int data = i & 0xf; + CACHE_BLOCK[i] = new BaseBlock(id, data); + } } public static boolean hasData(int id) { diff --git a/core/src/main/java/com/boydti/fawe/command/Reload.java b/core/src/main/java/com/boydti/fawe/command/Reload.java index 97014131..d25b987c 100644 --- a/core/src/main/java/com/boydti/fawe/command/Reload.java +++ b/core/src/main/java/com/boydti/fawe/command/Reload.java @@ -3,6 +3,7 @@ package com.boydti.fawe.command; import com.boydti.fawe.Fawe; import com.boydti.fawe.object.FaweCommand; import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.util.MainUtil; public class Reload extends FaweCommand { @@ -13,7 +14,7 @@ public class Reload extends FaweCommand { @Override public boolean execute(final FawePlayer player, final String... args) { Fawe.get().setupConfigs(); - player.sendMessage("&d[FAWE] Reloaded configuration"); + MainUtil.sendMessage(player, "&d[FAWE] Reloaded configuration"); return true; } } diff --git a/core/src/main/java/com/boydti/fawe/command/Undolist.java b/core/src/main/java/com/boydti/fawe/command/Undolist.java new file mode 100644 index 00000000..f78479eb --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/command/Undolist.java @@ -0,0 +1,16 @@ +package com.boydti.fawe.command; + +import com.boydti.fawe.object.FaweCommand; +import com.boydti.fawe.object.FawePlayer; + +public class Undolist extends FaweCommand { + + public Undolist() { + super("fawe.undolist"); + } + + @Override + public boolean execute(final FawePlayer player, final String... args) { + return false; + } +} diff --git a/core/src/main/java/com/boydti/fawe/config/BBC.java b/core/src/main/java/com/boydti/fawe/config/BBC.java index cb245ea3..24b308ad 100644 --- a/core/src/main/java/com/boydti/fawe/config/BBC.java +++ b/core/src/main/java/com/boydti/fawe/config/BBC.java @@ -24,6 +24,7 @@ public enum BBC { FIX_LIGHTING_SELECTION("&7Lighting has been fixed in %s0 chunks. Relog to see the affect.", "Info"), NO_REGION("&cYou have no current WorldEdit region", "Error"), SET_REGION("&7Selection set to your current WorldEdit region", "Info"), + WORLDEDIT_COMMAND_LIMIT("&7Please wait until your current action completes", "Info"), WORLDEDIT_DELAYED("&7Please wait while we process your WorldEdit action...", "Info"), WORLDEDIT_RUN("&7Apologies for the delay. Now executing: %s", "Info"), WORLDEDIT_COMPLETE("&7WorldEdit action completed.", "Info"), diff --git a/core/src/main/java/com/boydti/fawe/config/Settings.java b/core/src/main/java/com/boydti/fawe/config/Settings.java index 1dce84f5..8d2019a4 100644 --- a/core/src/main/java/com/boydti/fawe/config/Settings.java +++ b/core/src/main/java/com/boydti/fawe/config/Settings.java @@ -1,6 +1,8 @@ package com.boydti.fawe.config; import com.boydti.fawe.configuration.file.YamlConfiguration; +import com.boydti.fawe.object.FaweLimit; +import com.boydti.fawe.object.FawePlayer; import com.sk89q.worldedit.LocalSession; import java.io.File; import java.io.IOException; @@ -12,10 +14,6 @@ import java.util.Map.Entry; public class Settings { - public static int MAX_BLOCKSTATES = 1337; - public static int MAX_ENTITIES = 1337; - public static long WE_MAX_ITERATIONS = 1000; - public static long WE_MAX_VOLUME = 50000000; public static boolean REQUIRE_SELECTION = false; public static boolean FIX_ALL_LIGHTING = true; public static boolean COMMAND_PROCESSOR = false; @@ -27,6 +25,25 @@ public class Settings { public static int BUFFER_SIZE = 531441; public static boolean METRICS = true; + public static HashMap limits; + + public static FaweLimit getLimit(FawePlayer player) { + FaweLimit limit = new FaweLimit(); + for (Entry entry : limits.entrySet()) { + String key = entry.getKey(); + if (key.equals("default") || player.hasPermission("fawe.limit." + key)) { + FaweLimit newLimit = entry.getValue(); + limit.MAX_CHANGES = Math.max(limit.MAX_CHANGES, newLimit.MAX_CHANGES != -1 ? newLimit.MAX_CHANGES : Integer.MAX_VALUE); + limit.MAX_BLOCKSTATES = Math.max(limit.MAX_BLOCKSTATES, newLimit.MAX_BLOCKSTATES != -1 ? newLimit.MAX_BLOCKSTATES : Integer.MAX_VALUE); + limit.MAX_CHECKS = Math.max(limit.MAX_CHECKS, newLimit.MAX_CHECKS != -1 ? newLimit.MAX_CHECKS : Integer.MAX_VALUE); + limit.MAX_ENTITIES = Math.max(limit.MAX_ENTITIES, newLimit.MAX_ENTITIES != -1 ? newLimit.MAX_ENTITIES : Integer.MAX_VALUE); + limit.MAX_FAILS = Math.max(limit.MAX_FAILS, newLimit.MAX_FAILS != -1 ? newLimit.MAX_FAILS : Integer.MAX_VALUE); + limit.MAX_ITERATIONS = Math.max(limit.MAX_ITERATIONS, newLimit.MAX_ITERATIONS != -1 ? newLimit.MAX_ITERATIONS : Integer.MAX_VALUE); + } + } + return limit; + } + public static void setup(final File file) { if (!file.exists()) { file.getParentFile().mkdirs(); @@ -38,11 +55,9 @@ public class Settings { } final YamlConfiguration config = YamlConfiguration.loadConfiguration(file); + limits = new HashMap<>(); + final Map options = new HashMap<>(); - options.put("max-blockstates", MAX_BLOCKSTATES); - options.put("max-entities", MAX_ENTITIES); - options.put("max-iterations", WE_MAX_ITERATIONS); - options.put("max-volume", WE_MAX_VOLUME); options.put("require-selection-in-mask", REQUIRE_SELECTION); options.put("command-blacklist", WE_BLACKLIST); options.put("command-processor", COMMAND_PROCESSOR); @@ -53,6 +68,18 @@ public class Settings { options.put("history.compress", false); options.put("metrics", METRICS); + // Default limit + FaweLimit defaultLimit = new FaweLimit(); + if (!config.contains("limits.default")) { + config.createSection("limits.default"); + } + defaultLimit.load(config.getConfigurationSection("limits.default"), null, true); + for (String key : config.getConfigurationSection("limits").getKeys(false)) { + FaweLimit limit = new FaweLimit(); + limit.load(config.getConfigurationSection("limits." + key), defaultLimit, false); + } + + for (final Entry node : options.entrySet()) { if (!config.contains(node.getKey())) { config.set(node.getKey(), node.getValue()); @@ -60,10 +87,6 @@ public class Settings { } FIX_ALL_LIGHTING = config.getBoolean("fix-all-lighting"); COMMAND_PROCESSOR = config.getBoolean("command-processor"); - MAX_BLOCKSTATES = config.getInt("max-blockstates"); - MAX_ENTITIES = config.getInt("max-entities"); - WE_MAX_ITERATIONS = config.getInt("max-iterations"); - WE_MAX_VOLUME = config.getInt("max-volume"); MEM_FREE = config.getInt("max-memory-percent"); REQUIRE_SELECTION = config.getBoolean("require-selection-in-mask"); WE_BLACKLIST = config.getStringList("command-blacklist"); diff --git a/core/src/main/java/com/boydti/fawe/object/EditSessionWrapper.java b/core/src/main/java/com/boydti/fawe/object/EditSessionWrapper.java index 40037af7..900e6e7e 100644 --- a/core/src/main/java/com/boydti/fawe/object/EditSessionWrapper.java +++ b/core/src/main/java/com/boydti/fawe/object/EditSessionWrapper.java @@ -1,6 +1,7 @@ package com.boydti.fawe.object; import com.boydti.fawe.object.changeset.FaweChangeSet; +import com.boydti.fawe.util.FaweQueue; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.blocks.BlockType; @@ -26,7 +27,7 @@ public class EditSessionWrapper { return minY; } - public Extent getHistoryExtent(final Extent parent, FaweChangeSet set, final FawePlayer player) { - return new HistoryExtent(player.getLocation().world, parent, set); + public Extent getHistoryExtent(String world, FaweLimit limit, Extent parent, FaweChangeSet set, FaweQueue queue, FawePlayer player) { + return new HistoryExtent(world, limit, parent, set, queue); } } diff --git a/core/src/main/java/com/boydti/fawe/object/FaweLimit.java b/core/src/main/java/com/boydti/fawe/object/FaweLimit.java new file mode 100644 index 00000000..e285e500 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/FaweLimit.java @@ -0,0 +1,54 @@ +package com.boydti.fawe.object; + +import com.boydti.fawe.configuration.ConfigurationSection; +import java.util.HashMap; +import java.util.Map; + +/** + * Created by Jesse on 4/5/2016. + */ +public class FaweLimit { + public int MAX_CHANGES = 50000000; + public int MAX_FAILS = 50000000; + public int MAX_CHECKS = 50000000; + public int MAX_ITERATIONS = 1000; + public int MAX_BLOCKSTATES = 1337; + public int MAX_ENTITIES = 1337; + + public static FaweLimit MAX; + static { + MAX = new FaweLimit(); + MAX.MAX_CHANGES = Integer.MAX_VALUE; + MAX.MAX_FAILS = Integer.MAX_VALUE; + MAX.MAX_CHECKS = Integer.MAX_VALUE; + MAX.MAX_ITERATIONS = Integer.MAX_VALUE; + MAX.MAX_BLOCKSTATES = Integer.MAX_VALUE; + MAX.MAX_ENTITIES = Integer.MAX_VALUE; + } + + public boolean load(ConfigurationSection section, FaweLimit defaultLimit, boolean save) { + this.MAX_CHANGES = section.getInt("max-changes", defaultLimit == null ? MAX_CHANGES : defaultLimit.MAX_CHANGES); + this.MAX_FAILS = section.getInt("max-fails", defaultLimit == null ? MAX_FAILS : defaultLimit.MAX_FAILS); + this.MAX_CHECKS = section.getInt("max-checks", defaultLimit == null ? MAX_CHECKS : defaultLimit.MAX_CHECKS); + this.MAX_ITERATIONS = section.getInt("max-iterations", defaultLimit == null ? MAX_ITERATIONS : defaultLimit.MAX_ITERATIONS); + this.MAX_BLOCKSTATES = section.getInt("max-blockstates", defaultLimit == null ? MAX_BLOCKSTATES : defaultLimit.MAX_BLOCKSTATES); + this.MAX_ENTITIES = section.getInt("max-entities", defaultLimit == null ? MAX_ENTITIES : defaultLimit.MAX_ENTITIES); + boolean changed = false; + if (save) { + HashMap options = new HashMap<>(); + options.put("max-changes", MAX_CHANGES); + options.put("max-fails", MAX_FAILS); + options.put("max-checks", MAX_CHECKS); + options.put("max-iterations", MAX_ITERATIONS); + options.put("max-blockstates", MAX_BLOCKSTATES); + options.put("max-entities", MAX_ENTITIES); + for (Map.Entry entry : options.entrySet()) { + if (!section.contains(entry.getKey())) { + section.set(entry.getKey(), entry.getValue()); + changed = true; + } + } + } + return changed; + } +} diff --git a/core/src/main/java/com/boydti/fawe/object/FawePlayer.java b/core/src/main/java/com/boydti/fawe/object/FawePlayer.java index b65aeeb9..d6a5bb33 100644 --- a/core/src/main/java/com/boydti/fawe/object/FawePlayer.java +++ b/core/src/main/java/com/boydti/fawe/object/FawePlayer.java @@ -27,7 +27,7 @@ public abstract class FawePlayer { /** * The metadata map. */ - private ConcurrentHashMap meta; + private volatile ConcurrentHashMap meta; public static FawePlayer wrap(final Object obj) { return Fawe.imp().wrap(obj); @@ -67,6 +67,10 @@ public abstract class FawePlayer { } } + public FaweLimit getLimit() { + return Settings.getLimit(this); + } + public abstract String getName(); public abstract UUID getUUID(); diff --git a/core/src/main/java/com/boydti/fawe/object/HistoryExtent.java b/core/src/main/java/com/boydti/fawe/object/HistoryExtent.java index 83f3265f..c0a865d6 100644 --- a/core/src/main/java/com/boydti/fawe/object/HistoryExtent.java +++ b/core/src/main/java/com/boydti/fawe/object/HistoryExtent.java @@ -3,7 +3,6 @@ package com.boydti.fawe.object; import com.boydti.fawe.FaweCache; import com.boydti.fawe.object.changeset.FaweChangeSet; import com.boydti.fawe.util.FaweQueue; -import com.boydti.fawe.util.SetQueue; import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.blocks.BaseBlock; @@ -30,6 +29,7 @@ public class HistoryExtent extends AbstractDelegateExtent { private final com.boydti.fawe.object.changeset.FaweChangeSet changeSet; private final FaweQueue queue; + private final FaweLimit limit; /** * Create a new instance. @@ -37,9 +37,10 @@ public class HistoryExtent extends AbstractDelegateExtent { * @param extent the extent * @param changeSet the change set */ - public HistoryExtent(final String world, final Extent extent, final FaweChangeSet changeSet) { + public HistoryExtent(final String world, FaweLimit limit, final Extent extent, final FaweChangeSet changeSet, FaweQueue queue) { super(extent); - this.queue = SetQueue.IMP.getQueue(world); + this.limit = limit; + this.queue = queue; checkNotNull(changeSet); this.changeSet = changeSet; } diff --git a/core/src/main/java/com/boydti/fawe/object/changeset/DiskStorageHistory.java b/core/src/main/java/com/boydti/fawe/object/changeset/DiskStorageHistory.java index aab483d3..eeefcbeb 100644 --- a/core/src/main/java/com/boydti/fawe/object/changeset/DiskStorageHistory.java +++ b/core/src/main/java/com/boydti/fawe/object/changeset/DiskStorageHistory.java @@ -175,11 +175,13 @@ public class DiskStorageHistory implements ChangeSet, FaweChangeSet { try { OutputStream stream = getBAOS(x, y, z); //x - stream.write((x - ox) & 0xff); - stream.write(((x - ox) >> 8) & 0xff); + x-=ox; + stream.write((x) & 0xff); + stream.write(((x) >> 8) & 0xff); //z - stream.write((z - oz) & 0xff); - stream.write(((z - oz) >> 8) & 0xff); + z-=oz; + stream.write((z) & 0xff); + stream.write(((z) >> 8) & 0xff); //y stream.write((byte) y); //from @@ -375,8 +377,8 @@ public class DiskStorageHistory implements ChangeSet, FaweChangeSet { public Change read() { try { - int x = gis.read() + (gis.read() << 8) + ox; - int z = gis.read() + (gis.read() << 8) + oz; + int x = ((byte) gis.read() & 0xFF) + ((byte) gis.read() << 8) + ox; + int z = ((byte) gis.read() & 0xFF) + ((byte) gis.read() << 8) + oz; int y = gis.read() & 0xff; int from1 = gis.read(); int from2 = gis.read(); diff --git a/core/src/main/java/com/boydti/fawe/object/changeset/MemoryOptimizedHistory.java b/core/src/main/java/com/boydti/fawe/object/changeset/MemoryOptimizedHistory.java index ccde4d33..1760882a 100644 --- a/core/src/main/java/com/boydti/fawe/object/changeset/MemoryOptimizedHistory.java +++ b/core/src/main/java/com/boydti/fawe/object/changeset/MemoryOptimizedHistory.java @@ -64,11 +64,13 @@ public class MemoryOptimizedHistory implements ChangeSet, FaweChangeSet { try { OutputStream stream = getBAOS(x, y, z); //x - stream.write((x - ox) & 0xff); - stream.write(((x - ox) >> 8) & 0xff); + x -= ox; + z -= oz; + stream.write((x & 0xff)); + stream.write(((x >> 8) & 0xff)); //z - stream.write((z - oz) & 0xff); - stream.write(((z - oz) >> 8) & 0xff); + stream.write((z & 0xff)); + stream.write( ((z >> 8) & 0xff)); //y stream.write((byte) y); //from @@ -150,8 +152,6 @@ public class MemoryOptimizedHistory implements ChangeSet, FaweChangeSet { idsStream = new ByteArrayOutputStream(Settings.BUFFER_SIZE); idsStreamZip = new LZ4OutputStream(idsStream, Settings.BUFFER_SIZE, factory.fastCompressor()); if (Settings.COMPRESSION_LEVEL > 0) { -// Deflater deflater = new Deflater(Math.min(9, Settings.COMPRESSION_LEVEL), true); -// idsStreamZip = new DeflaterOutputStream(idsStreamZip, deflater, true); idsStreamZip = new LZ4OutputStream(idsStreamZip, Settings.BUFFER_SIZE, factory.highCompressor()); } ox = x; @@ -198,8 +198,8 @@ public class MemoryOptimizedHistory implements ChangeSet, FaweChangeSet { public Change read() { try { - int x = gis.read() + (gis.read() << 8) + ox; - int z = gis.read() + (gis.read() << 8) + oz; + int x = ((byte) gis.read() & 0xFF) + ((byte) gis.read() << 8) + ox; + int z = ((byte) gis.read() & 0xFF) + ((byte) gis.read() << 8) + oz; int y = gis.read() & 0xff; int from1 = gis.read(); int from2 = gis.read(); diff --git a/core/src/main/java/com/boydti/fawe/object/extent/FastWorldEditExtent.java b/core/src/main/java/com/boydti/fawe/object/extent/FastWorldEditExtent.java index 3aec4f54..3d40326b 100644 --- a/core/src/main/java/com/boydti/fawe/object/extent/FastWorldEditExtent.java +++ b/core/src/main/java/com/boydti/fawe/object/extent/FastWorldEditExtent.java @@ -2,7 +2,6 @@ package com.boydti.fawe.object.extent; import com.boydti.fawe.FaweCache; import com.boydti.fawe.util.FaweQueue; -import com.boydti.fawe.util.SetQueue; import com.boydti.fawe.util.TaskManager; import com.sk89q.worldedit.BlockVector; import com.sk89q.worldedit.EditSession; @@ -21,13 +20,11 @@ import java.util.List; public class FastWorldEditExtent extends AbstractDelegateExtent { - private final Thread thread; private final FaweQueue queue; - public FastWorldEditExtent(final World world, final Thread thread) { + public FastWorldEditExtent(final World world, FaweQueue queue) { super(world); - this.thread = thread; - this.queue = SetQueue.IMP.getQueue(world.getName()); + this.queue = queue; } @Override @@ -46,9 +43,7 @@ public class FastWorldEditExtent extends AbstractDelegateExtent { if (!queue.isChunkLoaded(position.getBlockX() >> 4, position.getBlockZ() >> 4)) { return EditSession.nullBiome; } - synchronized (this.thread) { - return super.getBiome(position); - } + return super.getBiome(position); } private BaseBlock lastBlock; @@ -67,24 +62,18 @@ public class FastWorldEditExtent extends AbstractDelegateExtent { return EditSession.nullBlock; } } - synchronized (this.thread) { - this.lastVector = position.toBlockVector(); - return this.lastBlock = super.getBlock(position); - } + this.lastVector = position.toBlockVector(); + return this.lastBlock = super.getBlock(position); } @Override public List getEntities() { - synchronized (this.thread) { - return super.getEntities(); - } + return super.getEntities(); } @Override public List getEntities(final Region region) { - synchronized (this.thread) { - return super.getEntities(region); - } + return super.getEntities(region); } @Override diff --git a/core/src/main/java/com/boydti/fawe/object/extent/ProcessedWEExtent.java b/core/src/main/java/com/boydti/fawe/object/extent/ProcessedWEExtent.java index 7c3d0786..51d06061 100644 --- a/core/src/main/java/com/boydti/fawe/object/extent/ProcessedWEExtent.java +++ b/core/src/main/java/com/boydti/fawe/object/extent/ProcessedWEExtent.java @@ -1,13 +1,10 @@ package com.boydti.fawe.object.extent; import com.boydti.fawe.FaweCache; -import com.boydti.fawe.config.BBC; -import com.boydti.fawe.config.Settings; +import com.boydti.fawe.object.FaweLimit; import com.boydti.fawe.object.FawePlayer; import com.boydti.fawe.object.RegionWrapper; import com.boydti.fawe.util.FaweQueue; -import com.boydti.fawe.util.MainUtil; -import com.boydti.fawe.util.SetQueue; import com.boydti.fawe.util.TaskManager; import com.boydti.fawe.util.WEManager; import com.sk89q.worldedit.BlockVector; @@ -29,30 +26,18 @@ import java.util.List; public class ProcessedWEExtent extends AbstractDelegateExtent { private final FaweQueue queue; + private final FaweLimit limit; private Extent parent; - private boolean BSblocked = false; - private boolean Eblocked = false; - private int BScount = 0; - private int Ecount = 0; - private int count = 0; - - private int max; private final FawePlayer user; private final HashSet mask; - private final Thread thread; - public ProcessedWEExtent(final World world, final Thread thread, final FawePlayer player, final HashSet mask, final int max) { + public ProcessedWEExtent(final World world, final FawePlayer player, final HashSet mask, FaweLimit limit, FaweQueue queue) { super(world); this.user = player; - this.queue = SetQueue.IMP.getQueue(world.getName()); - this.max = max != -1 ? max : Integer.MAX_VALUE; + this.queue = queue; this.mask = mask; - this.thread = thread; - } - - public void setMax(final int max) { - this.max = max != -1 ? max : Integer.MAX_VALUE; + this.limit = limit; } public void setParent(final Extent parent) { @@ -61,14 +46,9 @@ public class ProcessedWEExtent extends AbstractDelegateExtent { @Override public Entity createEntity(final Location location, final BaseEntity entity) { - if (this.Eblocked) { + if (limit.MAX_ENTITIES-- < 0) { return null; } - this.Ecount++; - if (this.Ecount > Settings.MAX_ENTITIES) { - this.Eblocked = true; - MainUtil.sendAdmin(BBC.WORLDEDIT_DANGEROUS_WORLDEDIT.format(queue.world + ": " + location.getBlockX() + "," + location.getBlockY() + "," + location.getBlockZ(), this.user)); - } if (WEManager.IMP.maskContains(this.mask, location.getBlockX(), location.getBlockZ())) { TaskManager.IMP.task(new Runnable() { @Override @@ -85,9 +65,7 @@ public class ProcessedWEExtent extends AbstractDelegateExtent { if (!queue.isChunkLoaded(position.getBlockX() >> 4, position.getBlockZ() >> 4)) { return EditSession.nullBiome; } - synchronized (this.thread) { - return super.getBiome(position); - } + return super.getBiome(position); } private BaseBlock lastBlock; @@ -95,6 +73,9 @@ public class ProcessedWEExtent extends AbstractDelegateExtent { @Override public BaseBlock getLazyBlock(final Vector position) { +// TODO get fast! +// TODO caches base blocks + if ((this.lastBlock != null) && this.lastVector.equals(position.toBlockVector())) { return this.lastBlock; } @@ -106,24 +87,18 @@ public class ProcessedWEExtent extends AbstractDelegateExtent { return EditSession.nullBlock; } } - synchronized (this.thread) { - this.lastVector = position.toBlockVector(); - return this.lastBlock = super.getLazyBlock(position); - } + this.lastVector = position.toBlockVector(); + return super.getLazyBlock(position); } @Override public List getEntities() { - synchronized (this.thread) { - return super.getEntities(); - } + return super.getEntities(); } @Override public List getEntities(final Region region) { - synchronized (this.thread) { - return super.getEntities(region); - } + return super.getEntities(region); } @Override @@ -171,18 +146,13 @@ public class ProcessedWEExtent extends AbstractDelegateExtent { case 33: case 151: case 178: { - if (this.BSblocked) { + if (limit.MAX_BLOCKSTATES-- < 0) { return false; } - this.BScount++; - if (this.BScount > Settings.MAX_BLOCKSTATES) { - this.BSblocked = true; - MainUtil.sendAdmin(BBC.WORLDEDIT_DANGEROUS_WORLDEDIT.format(queue.world + ": " + location.getBlockX() + "," + location.getBlockY() + "," + location.getBlockZ(), this.user)); - } final int x = location.getBlockX(); final int z = location.getBlockZ(); if (WEManager.IMP.maskContains(this.mask, x, z)) { - if (this.count++ > this.max) { + if (limit.MAX_CHANGES-- < 0) { if (this.parent != null) { WEManager.IMP.cancelEdit(this.parent); this.parent = null; @@ -204,6 +174,11 @@ public class ProcessedWEExtent extends AbstractDelegateExtent { } queue.setBlock(x, location.getBlockY(), z, id, FaweCache.hasData(id) ? (byte) block.getData() : 0); return true; + } else if (limit.MAX_FAILS-- < 0) { + if (this.parent != null) { + WEManager.IMP.cancelEdit(this.parent); + this.parent = null; + } } return false; } @@ -212,7 +187,7 @@ public class ProcessedWEExtent extends AbstractDelegateExtent { final int y = location.getBlockY(); final int z = location.getBlockZ(); if (WEManager.IMP.maskContains(this.mask, location.getBlockX(), location.getBlockZ())) { - if (this.count++ > this.max) { + if (limit.MAX_CHANGES--<0) { WEManager.IMP.cancelEdit(this.parent); this.parent = null; return false; @@ -306,6 +281,11 @@ public class ProcessedWEExtent extends AbstractDelegateExtent { return true; } } + } else if (limit.MAX_FAILS-- < 0) { + if (this.parent != null) { + WEManager.IMP.cancelEdit(this.parent); + this.parent = null; + } } return false; } diff --git a/core/src/main/java/com/boydti/fawe/regions/general/PlotSquaredFeature.java b/core/src/main/java/com/boydti/fawe/regions/general/PlotSquaredFeature.java index 8aaaf91e..c9e3f813 100644 --- a/core/src/main/java/com/boydti/fawe/regions/general/PlotSquaredFeature.java +++ b/core/src/main/java/com/boydti/fawe/regions/general/PlotSquaredFeature.java @@ -40,11 +40,7 @@ public class PlotSquaredFeature extends FaweMaskManager { final PlotId id = plot.getId(); boolean hasPerm = false; if (plot.owner != null) { - if (plot.owner.equals(pp.getUUID())) { - hasPerm = true; - } else if (plot.isAdded(pp.getUUID()) && pp.hasPermission("fawe.plotsquared.member")) { - hasPerm = true; - } + hasPerm = plot.isOwner(pp.getUUID()) || plot.getTrusted().contains(pp.getUUID()) || (plot.getMembers().contains(pp.getUUID()) && pp.hasPermission("fawe.plotsquared.member")); if (hasPerm) { RegionWrapper region = plot.getLargestRegion(); HashSet regions = plot.getRegions(); diff --git a/core/src/main/java/com/boydti/fawe/util/WEManager.java b/core/src/main/java/com/boydti/fawe/util/WEManager.java index 53d6bd54..c0717991 100644 --- a/core/src/main/java/com/boydti/fawe/util/WEManager.java +++ b/core/src/main/java/com/boydti/fawe/util/WEManager.java @@ -26,7 +26,6 @@ public class WEManager { } catch (final Exception e) { e.printStackTrace(); } - parent = null; } public boolean maskContains(final HashSet mask, final int x, final int z) { diff --git a/core/src/main/java/com/sk89q/worldedit/EditSession.java b/core/src/main/java/com/sk89q/worldedit/EditSession.java index d022080f..15123031 100644 --- a/core/src/main/java/com/sk89q/worldedit/EditSession.java +++ b/core/src/main/java/com/sk89q/worldedit/EditSession.java @@ -24,6 +24,7 @@ import com.boydti.fawe.FaweCache; import com.boydti.fawe.config.BBC; import com.boydti.fawe.config.Settings; import com.boydti.fawe.object.EditSessionWrapper; +import com.boydti.fawe.object.FaweLimit; import com.boydti.fawe.object.FawePlayer; import com.boydti.fawe.object.NullChangeSet; import com.boydti.fawe.object.RegionWrapper; @@ -35,8 +36,10 @@ import com.boydti.fawe.object.extent.NullExtent; import com.boydti.fawe.object.extent.ProcessedWEExtent; import com.boydti.fawe.object.extent.SafeExtentWrapper; import com.boydti.fawe.util.ExtentWrapper; +import com.boydti.fawe.util.FaweQueue; import com.boydti.fawe.util.MemUtil; import com.boydti.fawe.util.Perm; +import com.boydti.fawe.util.SetQueue; import com.boydti.fawe.util.TaskManager; import com.boydti.fawe.util.WEManager; import com.sk89q.worldedit.blocks.BaseBlock; @@ -153,14 +156,14 @@ public class EditSession implements Extent { protected final World world; private FaweChangeSet changeSet; private final EditSessionWrapper wrapper; - private @Nullable Extent changeSetExtent; private MaskingExtent maskingExtent; - private @Nullable ProcessedWEExtent processed; private final Extent bypassReorderHistory; private final Extent bypassHistory; private final Extent bypassNone; private boolean fastmode; private Mask oldMask; + private FaweLimit limit = FaweLimit.MAX; + private FaweQueue queue; public static BaseBiome nullBiome = new BaseBiome(0); public static BaseBlock nullBlock = new BaseBlock(0); @@ -190,8 +193,6 @@ public class EditSession implements Extent { this(WorldEdit.getInstance().getEventBus(), world, maxBlocks, blockBag, new EditSessionEvent(world, null, maxBlocks, null)); } - private final Thread thread; - private int changes = 0; private int maxBlocks; private BlockBag blockBag; @@ -212,7 +213,6 @@ public class EditSession implements Extent { this.blockBag = blockBag; this.maxBlocks = maxBlocks; - this.thread = Fawe.get().getMainThread(); this.world = world; this.wrapper = Fawe.imp().getEditSessionWrapper(this); // this.changeSet = new BlockOptimizedHistory(); @@ -227,10 +227,10 @@ public class EditSession implements Extent { return; } final Actor actor = event.getActor(); - + this.queue = SetQueue.IMP.getQueue(world.getName()); // Not a player; bypass history if ((actor == null) || !actor.isPlayer()) { - Extent extent = new FastWorldEditExtent(world, this.thread); + Extent extent = new FastWorldEditExtent(world, queue); // Everything bypasses extent = this.wrapExtent(extent, eventBus, event, Stage.BEFORE_CHANGE); extent = this.wrapExtent(extent, eventBus, event, Stage.BEFORE_REORDER); @@ -246,9 +246,10 @@ public class EditSession implements Extent { final String name = actor.getName(); final FawePlayer fp = FawePlayer.wrap(name); final LocalSession session = fp.getSession(); + this.fastmode = session.hasFastMode(); if (fp.hasWorldEditBypass()) { // Bypass skips processing and area restrictions - extent = new FastWorldEditExtent(world, this.thread); + extent = new FastWorldEditExtent(world, queue); if (this.hasFastMode()) { // Fastmode skips history and memory checks extent = this.wrapExtent(extent, eventBus, event, Stage.BEFORE_CHANGE); @@ -260,6 +261,7 @@ public class EditSession implements Extent { return; } } else { + this.limit = fp.getLimit(); final HashSet mask = WEManager.IMP.getMask(fp); if (mask.size() == 0) { if (Perm.hasPermission(fp, "fawe.admin")) { @@ -275,7 +277,8 @@ public class EditSession implements Extent { return; } // Process the WorldEdit action - extent = this.processed = new ProcessedWEExtent(world, this.thread, fp, mask, maxBlocks); + ProcessedWEExtent processed = new ProcessedWEExtent(world, fp, mask, limit, queue); + extent = processed; if (this.hasFastMode()) { // Fastmode skips history, masking, and memory checks extent = this.wrapExtent(extent, eventBus, event, Stage.BEFORE_CHANGE); @@ -283,7 +286,7 @@ public class EditSession implements Extent { extent = this.wrapExtent(extent, eventBus, event, Stage.BEFORE_HISTORY); extent = new ExtentWrapper(extent); // Set the parent. This allows efficient cancelling - this.processed.setParent(extent); + processed.setParent(extent); this.bypassReorderHistory = extent; this.bypassHistory = extent; this.bypassNone = extent; @@ -304,13 +307,13 @@ public class EditSession implements Extent { } // Perform memory checks after reorder extent = new SafeExtentWrapper(fp, extent); - this.processed.setParent(extent); + processed.setParent(extent); } // Include history, masking and memory checking. Extent wrapped; extent = wrapped = this.wrapExtent(extent, eventBus, event, Stage.BEFORE_CHANGE); extent = this.wrapExtent(extent, eventBus, event, Stage.BEFORE_REORDER); - extent = this.changeSetExtent = this.wrapper.getHistoryExtent(extent, this.changeSet, fp); + extent = this.wrapper.getHistoryExtent(world.getName(), limit, extent, this.changeSet, queue, fp); final Player skp = (Player) actor; final int item = skp.getItemInHand(); boolean hasMask = session.getMask() != null; @@ -386,10 +389,7 @@ public class EditSession implements Extent { * @param limit the limit (>= 0) or -1 for no limit */ public void setBlockChangeLimit(final int limit) { - if (this.processed != null) { - this.maxBlocks = limit; - this.processed.setMax(limit); - } + // Nothing } /** @@ -531,9 +531,7 @@ public class EditSession implements Extent { @Override public BaseBiome getBiome(final Vector2D position) { - synchronized (this.thread) { - return this.bypassNone.getBiome(position); - } + return this.bypassNone.getBiome(position); } @Override @@ -544,16 +542,19 @@ public class EditSession implements Extent { @Override public BaseBlock getLazyBlock(final Vector position) { - synchronized (this.thread) { - return this.world.getBlock(position); + if (limit != null && limit.MAX_CHECKS-- < 0) { + return nullBlock; } + int combinedId4Data = queue.getCombinedId4Data(position.getBlockX(), position.getBlockY(), position.getBlockZ()); + if (!FaweCache.hasNBT(combinedId4Data >> 4)) { + return FaweCache.CACHE_BLOCK[combinedId4Data]; + } + return this.world.getLazyBlock(position); } @Override - public synchronized BaseBlock getBlock(final Vector position) { - synchronized (this.thread) { - return this.world.getBlock(position); - } + public BaseBlock getBlock(final Vector position) { + return this.getLazyBlock(position); } /** @@ -564,10 +565,12 @@ public class EditSession implements Extent { * @deprecated Use {@link #getLazyBlock(Vector)} or {@link #getBlock(Vector)} */ @Deprecated - public synchronized int getBlockType(final Vector position) { - synchronized (this.thread) { - return this.world.getBlockType(position); + public int getBlockType(final Vector position) { + if (limit != null && limit.MAX_CHECKS-- < 0) { + return 0; } + int combinedId4Data = queue.getCombinedId4Data(position.getBlockX(), position.getBlockY(), position.getBlockZ()); + return combinedId4Data >> 4; } /** @@ -578,10 +581,12 @@ public class EditSession implements Extent { * @deprecated Use {@link #getLazyBlock(Vector)} or {@link #getBlock(Vector)} */ @Deprecated - public synchronized int getBlockData(final Vector position) { - synchronized (this.thread) { - return this.world.getBlockData(position); + public int getBlockData(final Vector position) { + if (limit != null && limit.MAX_CHECKS-- < 0) { + return 0; } + int combinedId4Data = queue.getCombinedId4Data(position.getBlockX(), position.getBlockY(), position.getBlockZ()); + return combinedId4Data & 0xF; } /** @@ -593,7 +598,7 @@ public class EditSession implements Extent { */ @Deprecated public BaseBlock rawGetBlock(final Vector position) { - return this.getBlock(position); + return this.getLazyBlock(position); } /** diff --git a/core/src/main/java/com/sk89q/worldedit/extension/platform/CommandManager.java b/core/src/main/java/com/sk89q/worldedit/extension/platform/CommandManager.java index e99cf99a..60fc2fdf 100644 --- a/core/src/main/java/com/sk89q/worldedit/extension/platform/CommandManager.java +++ b/core/src/main/java/com/sk89q/worldedit/extension/platform/CommandManager.java @@ -19,6 +19,9 @@ package com.sk89q.worldedit.extension.platform; +import com.boydti.fawe.Fawe; +import com.boydti.fawe.config.BBC; +import com.boydti.fawe.object.FawePlayer; import com.boydti.fawe.util.SetQueue; import com.boydti.fawe.util.TaskManager; import com.google.common.base.Joiner; @@ -235,7 +238,17 @@ public final class CommandManager { locals.put("arguments", event.getArguments()); long start = System.currentTimeMillis(); - + FawePlayer fp; + if (actor.isPlayer()) { + fp = Fawe.imp().wrap(actor.getName()); + if (fp.getMeta("fawe_action") != null) { + BBC.WORLDEDIT_COMMAND_LIMIT.send(fp); + return; + } + fp.setMeta("fawe_action", true); + } else { + fp = null; + } try { dispatcher.call(Joiner.on(" ").join(split), locals, new String[0]); } catch (CommandPermissionsException e) { @@ -266,6 +279,9 @@ public final class CommandManager { log.log(Level.SEVERE, "An unknown error occurred", e); } } finally { + if (fp != null) { + fp.deleteMeta("fawe_action"); + } EditSession editSession = locals.get(EditSession.class); if (editSession != null) { diff --git a/forge/.gradle/gradle.log b/forge/.gradle/gradle.log deleted file mode 100644 index d7ab305b..00000000 --- a/forge/.gradle/gradle.log +++ /dev/null @@ -1,89 +0,0 @@ -################################################# - ForgeGradle 2.1-SNAPSHOT-unknown - https://github.com/MinecraftForge/ForgeGradle -################################################# - Powered by MCP unknown - http://modcoderpack.com - by: Searge, ProfMobius, Fesh0r, - R4wk, ZeuX, IngisKahn, bspkrs -################################################# -Version string 'unspecified' does not match SemVer specification -You should try SemVer : http://semver.org/ -:core:compileJava -:forge:deobfCompileDummyTask -:forge:deobfProvidedDummyTask -:forge:extractDependencyATs SKIPPED -:forge:extractMcpData -:core:compileJava UP-TO-DATE -:core:processResources UP-TO-DATE -:core:classes UP-TO-DATE -:core:jar UP-TO-DATE -:forge:extractMcpData SKIPPED -:forge:extractMcpMappings SKIPPED -:forge:genSrgs SKIPPED -:forge:getVersionJson -:forge:downloadServer SKIPPED -:forge:splitServerJar SKIPPED -:forge:deobfMcMCP SKIPPED -:forge:sourceApiJava UP-TO-DATE -:forge:compileApiJava UP-TO-DATE -:forge:processApiResources UP-TO-DATE -:forge:apiClasses UP-TO-DATE -:forge:sourceMainJava -:forge:compileJavaC:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'modid()' in type 'Mod': class file for net.minecraftforge.fml.common.Mod not found -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'name()' in type 'Mod' -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'version()' in type 'Mod' -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'acceptableRemoteVersions()' in type 'Mod' -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'dependencies()' in type 'Mod' -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'acceptedMinecraftVersions()' in type 'Mod' -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'value()' in type 'Instance': class file for net.minecraftforge.fml.common.Mod$Instance not found -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'serverSide()' in type 'SidedProxy': class file for net.minecraftforge.fml.common.SidedProxy not found -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'clientSide()' in type 'SidedProxy' -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'modid()' in type 'Mod': class file for net.minecraftforge.fml.common.Mod not found -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'name()' in type 'Mod' -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'version()' in type 'Mod' -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'acceptableRemoteVersions()' in type 'Mod' -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'dependencies()' in type 'Mod' -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'acceptedMinecraftVersions()' in type 'Mod' -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'value()' in type 'Instance': class file for net.minecraftforge.fml.common.Mod$Instance not found -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'serverSide()' in type 'SidedProxy': class file for net.minecraftforge.fml.common.SidedProxy not found -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'clientSide()' in type 'SidedProxy' -Note: Writing plugin metadata to file:/C:/Users/Jesse/Desktop/OTHER/GitHub/FastAsyncWorldEdit/forge/build/classes/main/mcmod.info -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'modid()' in type 'Mod': class file for net.minecraftforge.fml.common.Mod not found -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'name()' in type 'Mod' -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'version()' in type 'Mod' -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'acceptableRemoteVersions()' in type 'Mod' -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'dependencies()' in type 'Mod' -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'acceptedMinecraftVersions()' in type 'Mod' -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'value()' in type 'Instance': class file for net.minecraftforge.fml.common.Mod$Instance not found -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'serverSide()' in type 'SidedProxy': class file for net.minecraftforge.fml.common.SidedProxy not found -C:\Users\Jesse\.gradle\caches\modules-2\files-2.1\com.sk89q.worldedit\worldedit-forge-mc1.8.9\6.1.1\dffd7e1882eba256eb2132fe315682c1d26522b1\worldedit-forge-mc1.8.9-6.1.1.jar(com/sk89q/worldedit/forge/ForgeWorldEdit.class): warning: Cannot find annotation method 'clientSide()' in type 'SidedProxy' -Note: Some input files use or override a deprecated API. -Note: Recompile with -Xlint:deprecation for details. -Note: Some input files use unchecked or unsafe operations. -Note: Recompile with -Xlint:unchecked for details. -27 warnings - -:forge:processResources UP-TO-DATE -:forge:classes -:forge:jar -:forge:sourceTestJava UP-TO-DATE -:forge:compileTestJava UP-TO-DATE -:forge:processTestResources UP-TO-DATE -:forge:testClasses UP-TO-DATE -:forge:test UP-TO-DATE -:forge:reobfJar -:forge:shadowJar -:forge:reobfShadowJar -:forge:extractRangemapReplacedMain -C:\Users\Jesse\Desktop\OTHER\GitHub\FastAsyncWorldEdit\forge\build\sources\main\java -:forge:retromapReplacedMain -remapping source... -:forge:sourceJar -:forge:assemble -:forge:check UP-TO-DATE -:forge:build - -BUILD SUCCESSFUL - -Total time: 36.523 secs diff --git a/forge/src/main/java/com/boydti/fawe/forge/FaweSponge.java b/forge/src/main/java/com/boydti/fawe/forge/FaweSponge.java index 805f052c..fabfb67a 100644 --- a/forge/src/main/java/com/boydti/fawe/forge/FaweSponge.java +++ b/forge/src/main/java/com/boydti/fawe/forge/FaweSponge.java @@ -19,7 +19,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Set; -import org.mcstats.Metrics; import org.spongepowered.api.Sponge; import org.spongepowered.api.entity.living.player.Player; import org.spongepowered.api.text.serializer.TextSerializers; @@ -129,7 +128,7 @@ public class FaweSponge implements IFawe { @Override public void startMetrics() { try { - Metrics metrics = new Metrics(Sponge.getGame(), Sponge.getPluginManager().fromInstance(plugin).get()); + SpongeMetrics metrics = new SpongeMetrics(Sponge.getGame(), Sponge.getPluginManager().fromInstance(plugin).get()); metrics.start(); debug("[FAWE] &6Metrics enabled."); } catch (IOException e) { diff --git a/forge/src/main/java/com/boydti/fawe/forge/SpongeMetrics.java b/forge/src/main/java/com/boydti/fawe/forge/SpongeMetrics.java new file mode 100644 index 00000000..e4450885 --- /dev/null +++ b/forge/src/main/java/com/boydti/fawe/forge/SpongeMetrics.java @@ -0,0 +1,518 @@ +package com.boydti.fawe.forge; + +/* + * Copyright 2011-2013 Tyler Blair. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and contributors and should not be interpreted as representing official policies, + * either expressed or implied, of anybody else. + */ + +import com.boydti.fawe.Fawe; +import com.google.inject.Inject; +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.Proxy; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLEncoder; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.zip.GZIPOutputStream; +import ninja.leaping.configurate.commented.CommentedConfigurationNode; +import ninja.leaping.configurate.hocon.HoconConfigurationLoader; +import ninja.leaping.configurate.loader.ConfigurationLoader; +import org.spongepowered.api.Game; +import org.spongepowered.api.plugin.PluginContainer; +import org.spongepowered.api.scheduler.Task; + +public class SpongeMetrics { + + /** + * The current revision number + */ + private final static int REVISION = 7; + + /** + * The base url of the metrics domain + */ + private static final String BASE_URL = "http://report.mcstats.org"; + + /** + * The url used to report a server's status + */ + private static final String REPORT_URL = "/plugin/%s"; + + /** + * Interval of time to ping (in minutes) + */ + private static final int PING_INTERVAL = 15; + + /** + * The game data is being sent for + */ + private final Game game; + + /** + * The plugin this metrics submits for + */ + private final PluginContainer plugin; + /** + * Lock for synchronization + */ + private final Object optOutLock = new Object(); + /** + * The plugin configuration file + */ + private CommentedConfigurationNode config; + /** + * The configuration loader + */ + private ConfigurationLoader configurationLoader; + /** + * The plugin configuration file + */ + private File configurationFile; + /** + * Unique server id + */ + private String guid; + /** + * Debug mode + */ + private boolean debug; + /** + * The scheduled task + */ + private volatile Task task = null; + + @Inject + public SpongeMetrics(final Game game, final PluginContainer plugin) throws IOException { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + + this.game = game; + this.plugin = plugin; + + loadConfiguration(); + } + + /** + * GZip compress a string of bytes + * + * @param input + * @return + */ + public static byte[] gzip(final String input) { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + GZIPOutputStream gzos = null; + + try { + gzos = new GZIPOutputStream(baos); + gzos.write(input.getBytes("UTF-8")); + } catch (final IOException e) { + e.printStackTrace(); + } finally { + if (gzos != null) { + try { + gzos.close(); + } catch (final IOException ignore) { + } + } + } + + return baos.toByteArray(); + } + + /** + * Appends a json encoded key/value pair to the given string builder. + * + * @param json + * @param key + * @param value + * @throws java.io.UnsupportedEncodingException + */ + private static void appendJSONPair(final StringBuilder json, final String key, final String value) throws UnsupportedEncodingException { + boolean isValueNumeric = false; + + try { + if (value.equals("0") || !value.endsWith("0")) { + Double.parseDouble(value); + isValueNumeric = true; + } + } catch (final NumberFormatException e) { + isValueNumeric = false; + } + + if (json.charAt(json.length() - 1) != '{') { + json.append(','); + } + + json.append(escapeJSON(key)); + json.append(':'); + + if (isValueNumeric) { + json.append(value); + } else { + json.append(escapeJSON(value)); + } + } + + /** + * Escape a string to create a valid JSON string + * + * @param text + * @return + */ + private static String escapeJSON(final String text) { + final StringBuilder builder = new StringBuilder(); + + builder.append('"'); + for (int index = 0; index < text.length(); index++) { + final char chr = text.charAt(index); + + switch (chr) { + case '"': + case '\\': + builder.append('\\'); + builder.append(chr); + break; + case '\b': + builder.append("\\b"); + break; + case '\t': + builder.append("\\t"); + break; + case '\n': + builder.append("\\n"); + break; + case '\r': + builder.append("\\r"); + break; + default: + if (chr < ' ') { + final String t = "000" + Integer.toHexString(chr); + builder.append("\\u" + t.substring(t.length() - 4)); + } else { + builder.append(chr); + } + break; + } + } + builder.append('"'); + + return builder.toString(); + } + + /** + * Encode text as UTF-8 + * + * @param text the text to encode + * @return the encoded text, as UTF-8 + */ + private static String urlEncode(final String text) throws UnsupportedEncodingException { + return URLEncoder.encode(text, "UTF-8"); + } + + /** + * Loads the configuration + */ + private void loadConfiguration() { + configurationFile = getConfigFile(); + configurationLoader = HoconConfigurationLoader.builder().setFile(configurationFile).build(); + + try { + if (!configurationFile.exists()) { + configurationFile.createNewFile(); + config = configurationLoader.load(); + + config.setComment("This contains settings for MCStats: http://mcstats.org"); + config.getNode("mcstats.guid").setValue(UUID.randomUUID().toString()); + config.getNode("mcstats.opt-out").setValue(false); + config.getNode("mcstats.debug").setValue(false); + + configurationLoader.save(config); + } else { + config = configurationLoader.load(); + } + + guid = config.getNode("mcstats.guid").getString(); + debug = config.getNode("mcstats.debug").getBoolean(); + } catch (final IOException e) { + e.printStackTrace(); + } + } + + /** + * Start measuring statistics. This will immediately create an async repeating task as the plugin and send the + * initial data to the metrics backend, and then after that it will post in increments of PING_INTERVAL * 1200 + * ticks. + * + * @return True if statistics measuring is running, otherwise false. + */ + public boolean start() { + synchronized (optOutLock) { + // Did we opt out? + if (isOptOut()) { + return false; + } + + // Is metrics already running? + if (task != null) { + return true; + } + + // Begin hitting the server with glorious data + final Task.Builder builder = game.getScheduler().createTaskBuilder(); + builder.async().interval(PING_INTERVAL, TimeUnit.MINUTES).execute(new Runnable() { + + private boolean firstPost = true; + + @Override + public void run() { + try { + // This has to be synchronized or it can collide with the disable method. + synchronized (optOutLock) { + // Disable Task, if it is running and the server owner decided to opt-out + if (isOptOut() && (task != null)) { + task.cancel(); + task = null; + } + } + + // We use the inverse of firstPost because if it is the first time we are posting, + // it is not a interval ping, so it evaluates to FALSE + // Each time thereafter it will evaluate to TRUE, i.e PING! + postPlugin(!firstPost); + + // After the first post we set firstPost to false + // Each post thereafter will be a ping + firstPost = false; + } catch (final IOException e) { + if (debug) { + Fawe.debug("[Metrics] " + e.getMessage()); + } + } + } + }); + return true; + } + } + + /** + * Has the server owner denied plugin metrics? + * + * @return true if metrics should be opted out of it + */ + public boolean isOptOut() { + synchronized (optOutLock) { + loadConfiguration(); + + return config.getNode("mcstats.opt-out").getBoolean(); + } + } + + /** + * Enables metrics for the server by setting "opt-out" to false in the config file and starting the metrics task. + * + * @throws java.io.IOException + */ + public void enable() throws IOException { + // This has to be synchronized or it can collide with the check in the task. + synchronized (optOutLock) { + // Check if the server owner has already set opt-out, if not, set it. + if (isOptOut()) { + config.getNode("mcstats.opt-out").setValue(false); + configurationLoader.save(config); + } + + // Enable Task, if it is not running + if (task == null) { + start(); + } + } + } + + /** + * Disables metrics for the server by setting "opt-out" to true in the config file and canceling the metrics task. + * + * @throws java.io.IOException + */ + public void disable() throws IOException { + // This has to be synchronized or it can collide with the check in the task. + synchronized (optOutLock) { + // Check if the server owner has already set opt-out, if not, set it. + if (!isOptOut()) { + config.getNode("mcstats.opt-out").setValue(true); + configurationLoader.save(config); + } + + // Disable Task, if it is running + if (task != null) { + task.cancel(); + task = null; + } + } + } + + /** + * Gets the File object of the config file that should be used to store data such as the GUID and opt-out status + * + * @return the File object for the config file + */ + public File getConfigFile() { + // TODO configDir + final File configFolder = new File("config"); + + return new File(configFolder, "PluginMetrics.conf"); + } + + /** + * Generic method that posts a plugin to the metrics website + * + */ + private void postPlugin(final boolean isPing) throws IOException { + // Server software specific section + final String pluginName = plugin.getName(); + final boolean onlineMode = game.getServer().getOnlineMode(); // TRUE if online mode is enabled + final String pluginVersion = plugin.getVersion().get(); + // TODO no visible way to get MC version at the moment + // TODO added by game.getPlatform().getMinecraftVersion() -- impl in 2.1 + final String serverVersion = String.format("%s %s", "Sponge", game.getPlatform().getMinecraftVersion()); + final int playersOnline = game.getServer().getOnlinePlayers().size(); + + // END server software specific section -- all code below does not use any code outside of this class / Java + + // Construct the post data + final StringBuilder json = new StringBuilder(1024); + json.append('{'); + + // The plugin's description file containg all of the plugin data such as name, version, author, etc + appendJSONPair(json, "guid", guid); + appendJSONPair(json, "plugin_version", pluginVersion); + appendJSONPair(json, "server_version", serverVersion); + appendJSONPair(json, "players_online", Integer.toString(playersOnline)); + + // New data as of R6 + final String osname = System.getProperty("os.name"); + String osarch = System.getProperty("os.arch"); + final String osversion = System.getProperty("os.version"); + final String java_version = System.getProperty("java.version"); + final int coreCount = Runtime.getRuntime().availableProcessors(); + + // normalize os arch .. amd64 -> x86_64 + if (osarch.equals("amd64")) { + osarch = "x86_64"; + } + + appendJSONPair(json, "osname", osname); + appendJSONPair(json, "osarch", osarch); + appendJSONPair(json, "osversion", osversion); + appendJSONPair(json, "cores", Integer.toString(coreCount)); + appendJSONPair(json, "auth_mode", onlineMode ? "1" : "0"); + appendJSONPair(json, "java_version", java_version); + + // If we're pinging, append it + if (isPing) { + appendJSONPair(json, "ping", "1"); + } + + // close json + json.append('}'); + + // Create the url + final URL url = new URL(BASE_URL + String.format(REPORT_URL, urlEncode(pluginName))); + + // Connect to the website + URLConnection connection; + + // Mineshafter creates a socks proxy, so we can safely bypass it + // It does not reroute POST requests so we need to go around it + if (isMineshafterPresent()) { + connection = url.openConnection(Proxy.NO_PROXY); + } else { + connection = url.openConnection(); + } + + final byte[] uncompressed = json.toString().getBytes(); + final byte[] compressed = gzip(json.toString()); + + // Headers + connection.addRequestProperty("User-Agent", "MCStats/" + REVISION); + connection.addRequestProperty("Content-Type", "application/json"); + connection.addRequestProperty("Content-Encoding", "gzip"); + connection.addRequestProperty("Content-Length", Integer.toString(compressed.length)); + connection.addRequestProperty("Accept", "application/json"); + connection.addRequestProperty("Connection", "close"); + + connection.setDoOutput(true); + + if (debug) { + Fawe.debug("[Metrics] Prepared request for " + pluginName + " uncompressed=" + uncompressed.length + " compressed=" + compressed.length); + } + + // Write the data + final OutputStream os = connection.getOutputStream(); + os.write(compressed); + os.flush(); + + // Now read the response + final BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); + String response = reader.readLine(); + + // close resources + os.close(); + reader.close(); + + if ((response == null) || response.startsWith("ERR") || response.startsWith("7")) { + if (response == null) { + response = "null"; + } else if (response.startsWith("7")) { + response = response.substring(response.startsWith("7,") ? 2 : 1); + } + + throw new IOException(response); + } + } + + /** + * Check if mineshafter is present. If it is, we need to bypass it to send POST requests + * + * @return true if mineshafter is installed on the server + */ + private boolean isMineshafterPresent() { + try { + Class.forName("mineshafter.MineServer"); + return true; + } catch (final Exception e) { + return false; + } + } + +} \ No newline at end of file