diff --git a/.gradle/7.1/executionHistory/executionHistory.bin b/.gradle/7.1/executionHistory/executionHistory.bin index e495d42..a7ee03c 100644 Binary files a/.gradle/7.1/executionHistory/executionHistory.bin and b/.gradle/7.1/executionHistory/executionHistory.bin differ diff --git a/.gradle/7.1/executionHistory/executionHistory.lock b/.gradle/7.1/executionHistory/executionHistory.lock index 65d9c53..35bce09 100644 Binary files a/.gradle/7.1/executionHistory/executionHistory.lock and b/.gradle/7.1/executionHistory/executionHistory.lock differ diff --git a/.gradle/7.1/fileHashes/fileHashes.bin b/.gradle/7.1/fileHashes/fileHashes.bin index 941f363..a7b1eb8 100644 Binary files a/.gradle/7.1/fileHashes/fileHashes.bin and b/.gradle/7.1/fileHashes/fileHashes.bin differ diff --git a/.gradle/7.1/fileHashes/fileHashes.lock b/.gradle/7.1/fileHashes/fileHashes.lock index bfae6db..10c249e 100644 Binary files a/.gradle/7.1/fileHashes/fileHashes.lock and b/.gradle/7.1/fileHashes/fileHashes.lock differ diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock index ec2c304..bd241e0 100644 Binary files a/.gradle/buildOutputCleanup/buildOutputCleanup.lock and b/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/.gradle/checksums/checksums.lock b/.gradle/checksums/checksums.lock index 0bb4c49..8139a0f 100644 Binary files a/.gradle/checksums/checksums.lock and b/.gradle/checksums/checksums.lock differ diff --git a/.gradle/checksums/md5-checksums.bin b/.gradle/checksums/md5-checksums.bin index 03fc8b5..9231a2d 100644 Binary files a/.gradle/checksums/md5-checksums.bin and b/.gradle/checksums/md5-checksums.bin differ diff --git a/.gradle/checksums/sha1-checksums.bin b/.gradle/checksums/sha1-checksums.bin index e96e373..73f81a2 100644 Binary files a/.gradle/checksums/sha1-checksums.bin and b/.gradle/checksums/sha1-checksums.bin differ diff --git a/eSpigot-Server/src/main/java/com/elevatemc/spigot/config/eSpigotConfig.java b/eSpigot-Server/src/main/java/com/elevatemc/spigot/config/eSpigotConfig.java index ce5c04f..de0a315 100644 --- a/eSpigot-Server/src/main/java/com/elevatemc/spigot/config/eSpigotConfig.java +++ b/eSpigot-Server/src/main/java/com/elevatemc/spigot/config/eSpigotConfig.java @@ -106,4 +106,50 @@ public class eSpigotConfig { obfuscatePlayerHealth = getBoolean( "settings.obfuscate-player-health", true ); } + + public static boolean fireEntityExplodeEvent; + private static void fireEntityExplodeEvent() + { + fireEntityExplodeEvent = getBoolean( "settings.fire-entity-explode-event", true ); + } + + public static boolean fastMath; + public static boolean fastMathCosSin; + private static void fastMath() + { + fastMath = getBoolean( "settings.fast-math", false ); + fastMathCosSin = getBoolean( "settings.fast-math-cos-sin", false ); + } + + public static boolean pandaWire; + private static void pandaWire() + { + pandaWire = getBoolean( "settings.panda-wire", true ); + } + + public static boolean lagCompensatedPotions; + public static boolean lagCompensatedPearls; + private static void lagCompensate() { + lagCompensatedPotions = getBoolean("settings.lag-compensated.potions", true); + lagCompensatedPearls = getBoolean("settings.lag-compensated.pearls", true); + } + + + public static boolean cannonTracker; + private static void cannonTracker() + { + cannonTracker = getBoolean( "settings.cannon-tracker", false ); + } + + public static boolean fixedPoolForTNT; + private static void fixedPoolForTNT() + { + fixedPoolForTNT = getBoolean( "tnt.fixed-pool", false ); + } + + public static int fixedPoolSizeForTNT; + private static void fixedPoolSizeForTNT() + { + fixedPoolSizeForTNT = getInt( "tnt.fixed-pool-size", 500 ); + } } diff --git a/eSpigot-Server/src/main/java/com/elevatemc/spigot/eSpigot.java b/eSpigot-Server/src/main/java/com/elevatemc/spigot/eSpigot.java index 5027946..69eadab 100644 --- a/eSpigot-Server/src/main/java/com/elevatemc/spigot/eSpigot.java +++ b/eSpigot-Server/src/main/java/com/elevatemc/spigot/eSpigot.java @@ -2,6 +2,7 @@ package com.elevatemc.spigot; import com.elevatemc.spigot.command.KnockbackCommand; import com.elevatemc.spigot.command.TicksPerSecondCommand; +import com.elevatemc.spigot.config.eSpigotConfig; import com.elevatemc.spigot.handler.MovementHandler; import com.elevatemc.spigot.handler.PacketHandler; import com.elevatemc.spigot.util.YamlConfig; @@ -34,6 +35,7 @@ public class eSpigot { public eSpigot(CraftServer server) { // Set instance of the server instance = this; + knockbackConfig = new YamlConfig("knockback.yml"); knockbackHandler = new KnockbackHandler(); diff --git a/eSpigot-Server/src/main/java/com/elevatemc/spigot/util/CryptException.java b/eSpigot-Server/src/main/java/com/elevatemc/spigot/exception/CryptException.java similarity index 88% rename from eSpigot-Server/src/main/java/com/elevatemc/spigot/util/CryptException.java rename to eSpigot-Server/src/main/java/com/elevatemc/spigot/exception/CryptException.java index 4a0dad3..a7af4a6 100644 --- a/eSpigot-Server/src/main/java/com/elevatemc/spigot/util/CryptException.java +++ b/eSpigot-Server/src/main/java/com/elevatemc/spigot/exception/CryptException.java @@ -1,5 +1,5 @@ // Taken from https://github.com/Elierrr/NachoSpigot/blob/b52603b25ff32b54c0b8df769e2271d187f92dc8/NachoSpigot-Server/src/main/java/me/elier/minecraft/util/CryptException.java -package com.elevatemc.spigot.util; +package com.elevatemc.spigot.exception; public class CryptException extends Exception { public CryptException(Throwable throwable) { diff --git a/eSpigot-Server/src/main/java/com/elevatemc/spigot/exception/ExploitException.java b/eSpigot-Server/src/main/java/com/elevatemc/spigot/exception/ExploitException.java new file mode 100644 index 0000000..698c7fa --- /dev/null +++ b/eSpigot-Server/src/main/java/com/elevatemc/spigot/exception/ExploitException.java @@ -0,0 +1,7 @@ +package com.elevatemc.spigot.exception; + +public class ExploitException extends RuntimeException { + public ExploitException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/eSpigot-Server/src/main/java/com/elevatemc/spigot/network/MinecraftPipeline.java b/eSpigot-Server/src/main/java/com/elevatemc/spigot/network/MinecraftPipeline.java new file mode 100644 index 0000000..baf47b8 --- /dev/null +++ b/eSpigot-Server/src/main/java/com/elevatemc/spigot/network/MinecraftPipeline.java @@ -0,0 +1,53 @@ +package com.elevatemc.spigot.network; + +import io.netty.buffer.PooledByteBufAllocator; +import io.netty.channel.*; +import io.netty.channel.socket.SocketChannel; +import io.netty.handler.timeout.ReadTimeoutHandler; +import net.minecraft.server.*; +import org.github.paperspigot.PaperSpigotConfig; + +public class MinecraftPipeline extends ChannelInitializer +{ + private final ServerConnection serverConnection; + + public MinecraftPipeline(ServerConnection serverConnection) { + this.serverConnection = serverConnection; + } + + protected void initChannel(SocketChannel channel) { + ChannelConfig config = channel.config(); + try { + config.setOption(ChannelOption.SO_KEEPALIVE, true); + } catch (Exception ignored) {} + try { + config.setOption(ChannelOption.TCP_NODELAY, true); + } catch (Exception ignored) {} + try { + config.setOption(ChannelOption.TCP_FASTOPEN, 1); + } catch (Exception ignored) {} + try { + config.setOption(ChannelOption.TCP_FASTOPEN_CONNECT, true); + } catch (Exception ignored) {} + try { + config.setOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); + } catch (Exception ignored) {} + try { + config.setOption(ChannelOption.IP_TOS, 0x18); + } catch (Exception ignored) {} + + if(PaperSpigotConfig.nettyReadTimeout) channel.pipeline().addLast("timeout", new ReadTimeoutHandler(30)); + + channel.pipeline() + .addLast("legacy_query", new LegacyPingHandler(serverConnection)) + .addLast("splitter", new PacketSplitter()) + .addLast("decoder", new PacketDecoder(EnumProtocolDirection.SERVERBOUND)) + .addLast("prepender", PacketPrepender.INSTANCE) // PandaSpigot - Share PacketPrepender instance + .addLast("encoder", new PacketEncoder(EnumProtocolDirection.CLIENTBOUND)); + + NetworkManager networkmanager = new NetworkManager(EnumProtocolDirection.SERVERBOUND); + this.serverConnection.pending.add(networkmanager); // Paper + channel.pipeline().addLast("packet_handler", networkmanager); + networkmanager.a(new HandshakeListener(this.serverConnection.server, networkmanager)); + } +} \ No newline at end of file diff --git a/eSpigot-Server/src/main/java/com/elevatemc/spigot/redstone/PandaRedstoneWire.java b/eSpigot-Server/src/main/java/com/elevatemc/spigot/redstone/PandaRedstoneWire.java new file mode 100644 index 0000000..81ae2e7 --- /dev/null +++ b/eSpigot-Server/src/main/java/com/elevatemc/spigot/redstone/PandaRedstoneWire.java @@ -0,0 +1,484 @@ +package com.elevatemc.spigot.redstone; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import net.minecraft.server.*; +import org.apache.commons.lang3.ArrayUtils; +import org.bukkit.event.block.BlockRedstoneEvent; + +import java.lang.reflect.Field; +import java.util.*; + +/* + * Based on https://gist.github.com/Panda4994/70ed6d39c89396570e062e4404a8d518 + * https://www.spigotmc.org/resources/pandawire-1-8-8-1-15-2.41991/ + * + * Mainly based on a decompiled version of PandaWire, with comments and structure + * from the original copied over. This get rid of any errors the decompiler made. + * + * On top of that theres additional optimisations in places. + */ +public class PandaRedstoneWire extends BlockRedstoneWire { + /* + * I considered replacing the lists with LinkedHashSets for faster lookup, + * but an artificial test showed barely any difference in performance + */ + /** Positions that need to be turned off **/ + private List turnOff = Lists.newArrayList(); + /** Positions that need to be checked to be turned on **/ + private List turnOn = Lists.newArrayList(); + /** Positions of wire that was updated already (Ordering determines update order and is therefore required!) **/ + private final Set updatedRedstoneWire = Sets.newLinkedHashSet(); + + /** Ordered arrays of the facings; Needed for the update order. + * I went with a vertical-first order here, but vertical last would work to. + * However it should be avoided to update the vertical axis between the horizontal ones as this would cause unneeded directional behavior. **/ + private static final EnumDirection[] facingsHorizontal = {EnumDirection.WEST, EnumDirection.EAST, EnumDirection.NORTH, EnumDirection.SOUTH}; + private static final EnumDirection[] facingsVertical = {EnumDirection.DOWN, EnumDirection.UP}; + private static final EnumDirection[] facings = ArrayUtils.addAll(facingsVertical, facingsHorizontal); + + /** Offsets for all surrounding blocks that need to receive updates **/ + private static final BaseBlockPosition[] surroundingBlocksOffset; + static { + Set set = Sets.newLinkedHashSet(); + for (EnumDirection facing : facings) { + set.add(ReflectUtil.getOfT(facing, BaseBlockPosition.class)); + } + + for (EnumDirection facing1 : facings) { + BaseBlockPosition v1 = ReflectUtil.getOfT(facing1, BaseBlockPosition.class); + + for (EnumDirection facing2 : facings) { + BaseBlockPosition v2 = ReflectUtil.getOfT(facing2, BaseBlockPosition.class); + set.add(new BlockPosition(v1.getX() + v2.getX(), v1.getY() + v2.getY(), v1.getZ() + v2.getZ())); + } + } + + set.remove(BlockPosition.ZERO); + surroundingBlocksOffset = set.toArray(new BaseBlockPosition[0]); + } + + private boolean canProvidePower = true; + + public PandaRedstoneWire() { + super(); + } + + private void updateSurroundingRedstone(World worldIn, BlockPosition pos, IBlockData iblockdata) { + // Recalculate the connected wires + calculateCurrentChanges(worldIn, pos, iblockdata); + + // Set to collect all the updates, to only execute them once. Ordering required. + Set blocksNeedingUpdate = Sets.newLinkedHashSet(); + + // Add the needed updates + for (BlockPosition posi : updatedRedstoneWire) { + addBlocksNeedingUpdate(worldIn, posi, blocksNeedingUpdate); + } + // Add all other updates to keep known behaviors + // They are added in a backwards order because it preserves a commonly used behavior with the update order + Iterator it = Lists.newLinkedList(updatedRedstoneWire).descendingIterator(); + while (it.hasNext()) { + addAllSurroundingBlocks(it.next(), blocksNeedingUpdate); + } + // Remove updates on the wires as they just were updated + blocksNeedingUpdate.removeAll(updatedRedstoneWire); + /* Avoid unnecessary updates on the just updated wires + * A huge scale test showed about 40% more ticks per second + * It's probably less in normal usage but likely still worth it + */ + updatedRedstoneWire.clear(); + + // Execute updates + for (BlockPosition posi : blocksNeedingUpdate) { + worldIn.d(posi, this); + } + } + + /** + * Turns on or off all connected wires + * + * @param worldIn World + * @param position Position of the wire that received the update + * @param state Current state of this block + */ + protected void calculateCurrentChanges(World worldIn, BlockPosition position, IBlockData state) { + // Turn off all connected wires first if needed + if (state.getBlock() == this) { + this.turnOff.add(position); + } else { + // In case this wire was removed, check the surrounding wires + checkSurroundingWires(worldIn, position); + } + + while (!turnOff.isEmpty()) { + BlockPosition pos = turnOff.remove(0); + state = worldIn.getType(pos); + int oldPower = state.get(POWER); + this.canProvidePower = false; + int blockPower = worldIn.A(pos); + this.canProvidePower = true; + int wirePower = getSurroundingWirePower(worldIn, pos); + // Lower the strength as it moved a block + --wirePower; + int newPower = Math.max(blockPower, wirePower); + + // Power lowered? + if (newPower < oldPower) { + // If it's still powered by a direct source (but weaker) mark for turn on + if (blockPower > 0 && !turnOn.contains(pos)) { + turnOn.add(pos); + } + // Set all the way to off for now, because wires that were powered by this need to update first + setWireState(worldIn, pos, state, 0); + // Power rose? + } else if (newPower > oldPower) { + // Set new Power + setWireState(worldIn, pos, state, newPower); + } + // Check if surrounding wires need to change based on the current/new state and add them to the lists + checkSurroundingWires(worldIn, pos); + } + + while (!turnOn.isEmpty()) { + BlockPosition pos = turnOn.remove(0); + state = worldIn.getType(pos); + int oldPower = state.get(POWER); + this.canProvidePower = false; + int blockPower = worldIn.A(pos); + this.canProvidePower = true; + int wirePower = getSurroundingWirePower(worldIn, pos); + // Lower the strength as it moved a block + wirePower--; + int newPower = Math.max(blockPower, wirePower); + + if (oldPower != newPower) { + BlockRedstoneEvent event = new BlockRedstoneEvent( + worldIn.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()), + oldPower, + newPower); + worldIn.getServer().getPluginManager().callEvent(event); + newPower = event.getNewCurrent(); + } + + if (newPower > oldPower) { + setWireState(worldIn, pos, state, newPower); + } + // Check if surrounding wires need to change based on the current/new state and add them to the lists + checkSurroundingWires(worldIn, pos); + } + + turnOff.clear(); + } + + /** + * Checks if an wire needs to be marked for update depending on the power next to it + * + * @author panda + * + * @param worldIn World + * @param pos Position of the wire that might need to change + * @param otherPower Power of the wire next to it + */ + protected void addWireToList(World worldIn, BlockPosition pos, int otherPower) { + IBlockData state = worldIn.getType(pos); + if (state.getBlock() == this) { + int power = state.get(POWER); + // Could get powered stronger by the neighbor? + if (power < otherPower - 1 && !turnOn.contains(pos)) { + // Mark for turn on check. + turnOn.add(pos); + } + // Should have powered the neighbor? Probably was powered by it and is in turn off phase. + if (power > otherPower && !turnOff.contains(pos)) { + // Mark for turn off check. + turnOff.add(pos); + } + } + } + + /** + * Checks if the wires around need to get updated depending on this wires state. + * Checks all wires below before the same layer before on top to keep + * some more rotational symmetry around the y-axis. + * + * @author panda + * + * @param worldIn World + * @param pos Position of the wire + */ + protected void checkSurroundingWires(World worldIn, BlockPosition pos) { + IBlockData state = worldIn.getType(pos); + int ownPower = 0; + if (state.getBlock() == Blocks.REDSTONE_WIRE) { + ownPower = state.get(POWER); + } + // Check wires on the same layer first as they appear closer to the wire + for (EnumDirection facingHorizontal : facingsHorizontal) { + this.addWireToList(worldIn, pos.shift(facingHorizontal), ownPower); + } + for (EnumDirection facingVertical : facingsVertical) { + BlockPosition offsetPos = pos.shift(facingVertical); + Block block = worldIn.getType(offsetPos).getBlock(); + boolean solidBlock = block.u(); + for (EnumDirection facingHorizontal : facingsHorizontal) { + // wire can travel upwards if the block on top doesn't cut the wire (is non-solid) + // it can travel down if the block below is solid and the block "diagonal" doesn't cut off the wire (is non-solid) + if (facingVertical == EnumDirection.UP && (!solidBlock || /* This can improve glowstone wiring up to 2.5x */ block == Blocks.GLOWSTONE) || facingVertical == EnumDirection.DOWN && solidBlock && !worldIn.getType(offsetPos.shift(facingHorizontal)).getBlock().isOccluding()) { + this.addWireToList(worldIn, offsetPos.shift(facingHorizontal), ownPower); + } + } + } + } + + /** + * Gets the maximum power of the surrounding wires + * + * @author panda + * + * @param worldIn World + * @param pos Position of the asking wire + * @return The maximum power of the wires that could power the wire at pos + */ + private int getSurroundingWirePower(World worldIn, BlockPosition pos) { + int wirePower = 0; + for (EnumDirection enumfacing : EnumDirection.EnumDirectionLimit.HORIZONTAL) { + BlockPosition offsetPos = pos.shift(enumfacing); + IBlockData iblockdata = worldIn.getType(offsetPos); + boolean occluding = iblockdata.getBlock().isOccluding(); + // Wires on the same layer + wirePower = this.getPower(iblockdata, wirePower); + + // Block below the wire need to be solid (Upwards diode of slabs/stairs/glowstone) and no block should cut the wire + if (occluding && !worldIn.getType(pos.up()).getBlock().isOccluding()) { + wirePower = this.getPower(worldIn, offsetPos.up(), wirePower); + // Only get from power below if no block is cutting the wire + } else if (!occluding) { + wirePower = this.getPower(worldIn, offsetPos.down(), wirePower); + } + } + return wirePower; + } + + /** + * Adds all blocks that need to receive an update from a redstone change in this position. + * This means only blocks that actually could change. + * + * @author panda + * + * @param worldIn World + * @param pos Position of the wire + * @param set Set to add the update positions too + */ + private void addBlocksNeedingUpdate(World worldIn, BlockPosition pos, Set set) { + Set connectedSides = getSidesToPower(worldIn, pos); + // Add the blocks next to the wire first (closest first order) + for (EnumDirection facing : facings) { + BlockPosition offsetPos = pos.shift(facing); + IBlockData state = worldIn.getType(offsetPos); + // canConnectTo() is not the nicest solution here as it returns true for e.g. the front of a repeater + // canBlockBePowereFromSide catches these cases + boolean flag = connectedSides.contains(facing.opposite()) || facing == EnumDirection.DOWN; + if (flag || (facing.k().c() && a(state, facing))) { + if (canBlockBePoweredFromSide(state, facing, true)) set.add(offsetPos); + } + + // Later add blocks around the surrounding blocks that get powered + if (flag && worldIn.getType(offsetPos).getBlock().isOccluding()) { + for (EnumDirection facing1 : facings) { + if (canBlockBePoweredFromSide(worldIn.getType(offsetPos.shift(facing1)), facing1, false)) set.add(offsetPos.shift(facing1)); + } + } + } + } + + /** + * Checks if a block can get powered from a side. + * This behavior would better be implemented per block type as follows: + * - return false as default. (blocks that are not affected by redstone don't need to be updated, it doesn't really hurt if they are either) + * - return true for all blocks that can get powered from all side and change based on it (doors, fence gates, trap doors, note blocks, lamps, dropper, hopper, TNT, rails, possibly more) + * - implement own logic for pistons, repeaters, comparators and redstone torches + * The current implementation was chosen to keep everything in one class. + * + * Why is this extra check needed? + * 1. It makes sure that many old behaviors still work (QC + Pistons). + * 2. It prevents updates from "jumping". + * Or rather it prevents this wire to update a block that would get powered by the next one of the same line. + * This is to prefer as it makes understanding the update order of the wire really easy. The signal "travels" from the power source. + * + * @author panda + * + * @param state State of the block + * @param side Side from which it gets powered + * @param isWire True if it's powered by a wire directly, False if through a block + * @return True if the block can change based on the power level it gets on the given side, false otherwise + */ + private boolean canBlockBePoweredFromSide(IBlockData state, EnumDirection side, boolean isWire) { + Block block = state.getBlock(); + if (block == Blocks.AIR) return false; + if (block instanceof BlockPiston && state.get(BlockPiston.FACING) == side.opposite()) return false; + if (block instanceof BlockDiodeAbstract && state.get(BlockDiodeAbstract.FACING) != side.opposite()) + return isWire && block instanceof BlockRedstoneComparator && state.get(BlockRedstoneComparator.FACING).k() != side.k() && side.k().c(); + return !(state.getBlock() instanceof BlockRedstoneTorch) || (!isWire && state.get(BlockRedstoneTorch.FACING) == side); + } + + /** + * Creates a list of all horizontal sides that can get powered by a wire. + * The list is ordered the same as the facingsHorizontal. + * + * @param worldIn World + * @param pos Position of the wire + * @return List of all facings that can get powered by this wire + */ + private Set getSidesToPower(World worldIn, BlockPosition pos) { + Set retval = Sets.newHashSet(); + for (EnumDirection facing : facingsHorizontal) { + if (this.isPowerSourceAt(worldIn, pos, facing)) { + retval.add(facing); + } + } + if (retval.isEmpty()) return Sets.newHashSet(facingsHorizontal); + boolean northsouth = retval.contains(EnumDirection.NORTH) || retval.contains(EnumDirection.SOUTH); + boolean eastwest = retval.contains(EnumDirection.EAST) || retval.contains(EnumDirection.WEST); + if (northsouth) { + retval.remove(EnumDirection.EAST); + retval.remove(EnumDirection.WEST); + } + if (eastwest) { + retval.remove(EnumDirection.NORTH); + retval.remove(EnumDirection.SOUTH); + } + return retval; + } + + private boolean canSidePower(World worldIn, BlockPosition pos, EnumDirection side) { + Set retval = Sets.newHashSet(); + for (EnumDirection facing : facingsHorizontal) { + if (this.isPowerSourceAt(worldIn, pos, facing)) { + retval.add(facing); + } + } + if (retval.isEmpty()) { + return true; + } + boolean northsouth = retval.contains(EnumDirection.NORTH) || retval.contains(EnumDirection.SOUTH); + boolean eastwest = retval.contains(EnumDirection.EAST) || retval.contains(EnumDirection.WEST); + if (northsouth) { + retval.remove(EnumDirection.EAST); + retval.remove(EnumDirection.WEST); + } + if (eastwest) { + retval.remove(EnumDirection.NORTH); + retval.remove(EnumDirection.SOUTH); + } + return retval.contains(side); + } + + /** + * Adds all surrounding positions to a set. + * This is the neighbor blocks, as well as their neighbors + * + * @param pos + * @param set + */ + private void addAllSurroundingBlocks(BlockPosition pos, Set set) { + for (BaseBlockPosition vect : surroundingBlocksOffset) { + set.add(pos.a(vect)); + } + } + + /** + * Sets the block state of a wire with a new power level and marks for updates + * + * @author panda + * + * @param worldIn World + * @param pos Position at which the state needs to be set + * @param state Old state + * @param power Power it should get set to + */ + private void setWireState(World worldIn, BlockPosition pos, IBlockData state, int power) { + state = state.set(POWER, power); + worldIn.setTypeAndData(pos, state, 2); + updatedRedstoneWire.add(pos); + } + + public void onPlace(World world, BlockPosition blockposition, IBlockData iblockdata) { + this.updateSurroundingRedstone(world, blockposition, world.getType(blockposition)); + + for (EnumDirection enumdirection : EnumDirection.values()) { + world.applyPhysics(blockposition.shift(enumdirection), this); + } + } + + public void remove(World world, BlockPosition blockposition, IBlockData iblockdata) { + for (EnumDirection enumdirection : EnumDirection.values()) { + world.applyPhysics(blockposition.shift(enumdirection), this); + } + + this.updateSurroundingRedstone(world, blockposition, world.getType(blockposition)); + } + + public void doPhysics(World world, BlockPosition blockposition, IBlockData iblockdata, Block block) { + if (this.canPlace(world, blockposition)) { + this.updateSurroundingRedstone(world, blockposition, iblockdata); + } else { + this.b(world, blockposition, iblockdata, 0); + world.setAir(blockposition); + } + } + + protected final int getPower(IBlockData state, int power) { + if (state.getBlock() != Blocks.REDSTONE_WIRE) { + return power; + } + int j = state.get(BlockRedstoneWire.POWER); + return Math.max(j, power); + } + + public int a(IBlockAccess iblockaccess, BlockPosition blockposition, IBlockData iblockdata, EnumDirection enumdirection) { + if (!this.canProvidePower) { + return 0; + } else { + int i = iblockdata.get(BlockRedstoneWire.POWER); + if (i == 0) { // md_5 change? This isn't in the original. + return 0; + } else if (enumdirection == EnumDirection.UP) { + return i; + } else { + return this.canSidePower((World) iblockaccess, blockposition, enumdirection) ? i : 0; + } + } + } + + private boolean isPowerSourceAt(IBlockAccess iblockaccess, BlockPosition blockposition, EnumDirection enumdirection) { + BlockPosition blockpos = blockposition.shift(enumdirection); + IBlockData iblockdata = iblockaccess.getType(blockpos); + Block block = iblockdata.getBlock(); + boolean flag = block.isOccluding(); + boolean flag1 = iblockaccess.getType(blockposition.up()).getBlock().isOccluding(); + return !flag1 && flag && e(iblockaccess, blockpos.up()) || (a(iblockdata, enumdirection) || (block == Blocks.POWERED_REPEATER && iblockdata.get(BlockDiodeAbstract.FACING) == enumdirection || !flag && e(iblockaccess, blockpos.down()))); + } + + public static class ReflectUtil { + public static T getOfT(Object obj, Class type) { + for (Field field : obj.getClass().getDeclaredFields()) { + if (type.equals(field.getType())) { + return get(obj, field, type); + } + } + + return null; + } + + public static T get(Object obj, Field field, Class type) { + try { + field.setAccessible(true); + return type.cast(field.get(obj)); + } catch (ReflectiveOperationException ex) { + ex.printStackTrace(); + return null; + } + } + } +} \ No newline at end of file diff --git a/eSpigot-Server/src/main/java/com/elevatemc/spigot/threading/ThreadingManager.java b/eSpigot-Server/src/main/java/com/elevatemc/spigot/threading/ThreadingManager.java index 9a2672c..8555016 100644 --- a/eSpigot-Server/src/main/java/com/elevatemc/spigot/threading/ThreadingManager.java +++ b/eSpigot-Server/src/main/java/com/elevatemc/spigot/threading/ThreadingManager.java @@ -1,5 +1,6 @@ package com.elevatemc.spigot.threading; +import com.elevatemc.spigot.config.eSpigotConfig; import com.elevatemc.spigot.pathsearch.PathSearchThrottlerThread; import com.elevatemc.spigot.pathsearch.jobs.PathSearchJob; import net.minecraft.server.NBTCompressedStreamTools; @@ -35,6 +36,8 @@ public class ThreadingManager { private TaskQueueWorker nbtFiles; private TaskQueueWorker headConversions; + public static ThreadPoolExecutor asyncExplosionsExecutor; + public ThreadingManager() { instance = this; this.pathSearchThrottler = new PathSearchThrottlerThread(2); @@ -44,12 +47,18 @@ public class ThreadingManager { this.cachedThreadPool = Executors.newCachedThreadPool(this.cachedThreadPoolFactory); this.nbtFiles = this.createTaskQueueWorker(); this.headConversions = this.createTaskQueueWorker(); + + if (eSpigotConfig.fixedPoolForTNT) + asyncExplosionsExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool(eSpigotConfig.fixedPoolSizeForTNT); + else + asyncExplosionsExecutor = (ThreadPoolExecutor) Executors.newCachedThreadPool(); } public void shutdown() { this.pathSearchThrottler.shutdown(); this.timerService.shutdown(); this.cachedThreadPool.shutdown(); + this.asyncExplosionsExecutor.shutdown(); while((this.nbtFiles.isActive()) && !this.cachedThreadPool.isTerminated()) { try { this.cachedThreadPool.awaitTermination(10, TimeUnit.SECONDS); diff --git a/eSpigot-Server/src/main/java/com/elevatemc/spigot/util/ObjectMapList.java b/eSpigot-Server/src/main/java/com/elevatemc/spigot/util/ObjectMapList.java new file mode 100644 index 0000000..79cfc9b --- /dev/null +++ b/eSpigot-Server/src/main/java/com/elevatemc/spigot/util/ObjectMapList.java @@ -0,0 +1,202 @@ +package com.elevatemc.spigot.util; + +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.AbstractReferenceList; +import it.unimi.dsi.fastutil.objects.ObjectListIterator; +import it.unimi.dsi.fastutil.objects.ObjectSpliterator; + +import java.util.Arrays; +import java.util.NoSuchElementException; +import java.util.Set; + +/** + * list with O(1) remove & contains + * @author Spottedleaf + */ +@SuppressWarnings("unchecked") +public final class ObjectMapList extends AbstractReferenceList implements Set { + + protected final Int2IntOpenHashMap objectToIndex; + + protected static final Object[] EMPTY_LIST = new Object[0]; + protected T[] elements = (T[]) EMPTY_LIST; + protected int count; + + public ObjectMapList() { + this(2, 0.8f); + } + + public ObjectMapList(int expectedSize, float loadFactor) { + this.objectToIndex = new Int2IntOpenHashMap(expectedSize, loadFactor); + this.objectToIndex.defaultReturnValue(Integer.MIN_VALUE); + } + + @Override + public int size() { + return this.count; + } + + @Override + public int indexOf(Object object) { + return this.objectToIndex.get(object.hashCode()); + } + + @Override + public int lastIndexOf(Object object) { + return super.indexOf(object); + } + + @Override + public boolean remove(final Object object) { + final int index = this.objectToIndex.remove(object.hashCode()); + if (index == Integer.MIN_VALUE) { + return false; + } + + // move the obj at the end to this index + final int endIndex = --this.count; + final T end = this.elements[endIndex]; + if (index != endIndex) { + // not empty after this call + this.objectToIndex.put(end.hashCode(), index); // update index + } + this.elements[index] = end; + this.elements[endIndex] = null; + return true; + } + + @Override + public boolean add(final T object) { + final int count = this.count; + final int currIndex = this.objectToIndex.putIfAbsent(object.hashCode(), count); + + if (currIndex != Integer.MIN_VALUE) { + return false; // already in this list + } + + T[] list = this.elements; + if (list.length == count) { + // resize required + list = this.elements = Arrays.copyOf(list, (int)Math.max(4L, (long) count << 1)); // overflow results in negative + } + + list[count] = object; + this.count = count + 1; + return true; + } + + @Override + public void add(final int index, final T object) { + final int currIndex = this.objectToIndex.putIfAbsent(object.hashCode(), index); + + if (currIndex != Integer.MIN_VALUE) { + return; // already in this list + } + + int count = this.count; + T[] list = this.elements; + if (list.length == count) { + // resize required + list = this.elements = Arrays.copyOf(list, (int) Math.max(4L, (long) count << 1)); // overflow results in negative + } + + System.arraycopy(list, index, list, index + 1, count - index); + list[index] = object; + this.count = count + 1; + } + + @Override + public T get(int index) { + return this.elements[index]; + } + + @Override + public boolean isEmpty() { + return this.count == 0; + } + + public T[] getRawData() { + return this.elements; + } + + @Override + public void clear() { + this.objectToIndex.clear(); + Arrays.fill(this.elements, 0, this.count, null); + this.count = 0; + } + + @Override + public Object[] toArray() { + return Arrays.copyOf(this.elements, this.count); + } + + @Override + public ObjectSpliterator spliterator() { + return super.spliterator(); + } + + @Override + public ObjectListIterator iterator() { + return new Iterator(0); + } + + private class Iterator implements ObjectListIterator { + + T lastRet; + int current; + + Iterator(int index) { + current = index; + } + + @Override + public int nextIndex() { + return this.current + 1; + } + + @Override + public int previousIndex() { + return this.current - 1; + } + + @Override + public boolean hasNext() { + return this.current < ObjectMapList.this.count; + } + + @Override + public boolean hasPrevious() { + return this.current > 0; + } + + @Override + public T next() { + if (this.current >= ObjectMapList.this.count) { + throw new NoSuchElementException(); + } + return this.lastRet = ObjectMapList.this.elements[this.current++]; + } + + @Override + public T previous() { + if (this.current < 0) { + throw new NoSuchElementException(); + } + return this.lastRet = ObjectMapList.this.elements[--this.current]; + } + + @Override + public void remove() { + final T lastRet = this.lastRet; + + if (lastRet == null) { + throw new IllegalStateException(); + } + this.lastRet = null; + + ObjectMapList.this.remove(lastRet); + --this.current; + } + } +} \ No newline at end of file diff --git a/eSpigot-Server/src/main/java/com/elevatemc/spigot/util/OptimizedWorldTileEntitySet.java b/eSpigot-Server/src/main/java/com/elevatemc/spigot/util/OptimizedWorldTileEntitySet.java new file mode 100644 index 0000000..3905769 --- /dev/null +++ b/eSpigot-Server/src/main/java/com/elevatemc/spigot/util/OptimizedWorldTileEntitySet.java @@ -0,0 +1,131 @@ +package com.elevatemc.spigot.util; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Iterators; +import com.google.common.collect.Multimap; +import it.unimi.dsi.fastutil.objects.Object2LongMap; +import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; +import net.minecraft.server.BlockJukeBox.TileEntityRecordPlayer; +import net.minecraft.server.*; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + + +/** + * Optimized world tile entity list implementation, provides + * an iterator of tile entities that need to be ticked based + * on the world time to reduce needed iteration/checks. + * @author Rastrian + */ +public final class OptimizedWorldTileEntitySet extends AbstractSet { + + /** + * Map of tile classes with modified tick intervals. + */ + private static final Object2LongMap> CUSTOM_TICK_INTERVALS = + new Object2LongOpenHashMap>() {{ + // Entities with empty tick# methods. + this.put(TileEntityCommand.class, -1L); + this.put(TileEntityComparator.class, -1L); + this.put(TileEntityDispenser.class, -1L); + this.put(TileEntityDropper.class, -1L); + this.put(TileEntityEnderPortal.class, -1L); + this.put(TileEntityFlowerPot.class, -1L); + this.put(TileEntityNote.class, -1L); + this.put(TileEntityRecordPlayer.class, -1L); + this.put(TileEntitySign.class, -1L); + this.put(TileEntitySkull.class, -1L); + + // Entities that we have modified to have empty tick# methods. + this.put(TileEntityEnderChest.class, -1L); + this.put(TileEntityChest.class, -1L); + + // Entities with vanilla controlled tick id checks inside + // the tick# methods, helps to schedule only when required. + this.put(TileEntityBeacon.class, 80L); + this.put(TileEntityLightDetector.class, 20L); + + // Entities that do a scanPlayer# lookup, helps to slow down. + this.put(TileEntityEnchantTable.class, 20L); + this.put(TileEntityMobSpawner.class, 20L); + }}; + + /** + * Multimap of all registered tile entities. + */ + private final Multimap, TileEntity> registeredTiles = HashMultimap.create(); + + @Override + public int size() { + return this.registeredTiles.size(); + } + + @Override + public boolean add(TileEntity tile) { + if (tile == null) { + return false; + } + + if (this.registeredTiles.containsValue(tile)) { + return false; + } + + return this.registeredTiles.put(tile.getClass(), tile); + } + + @Override + public boolean remove(Object object) { + if (object == null) { + return false; + } + + return this.registeredTiles.remove(object.getClass(), object); + } + + @Override + public boolean contains(Object object) { + if (object == null) { + return false; + } + + return this.registeredTiles.containsEntry(object.getClass(), object); + } + + @Override + public void clear() { + this.registeredTiles.clear(); + } + + @Override + public @NotNull Iterator iterator() { + return this.registeredTiles.values().iterator(); + } + + public Iterator tickIterator(long worldTime) { + LinkedList> tileIterators = new LinkedList<>(); + for (Class tileClassToTick : this.getTileClassesToTick(worldTime)) { + Collection tileBucket = this.registeredTiles.get(tileClassToTick); + if (tileBucket != null) { + tileIterators.add(tileBucket.iterator()); + } + } + + return Iterators.concat(tileIterators.iterator()); + } + + private List> getTileClassesToTick(long worldTime) { + List> tilesToTick = new LinkedList<>(); + for (Class registeredTileClass : this.registeredTiles.keySet()) { + long customTickInterval = OptimizedWorldTileEntitySet.CUSTOM_TICK_INTERVALS.getLong(registeredTileClass); + if (customTickInterval != 0) { // Troves non-existent value is 0. + if (customTickInterval > 0 && (worldTime == 0 || worldTime % customTickInterval == 0)) { + tilesToTick.add(registeredTileClass); + } + continue; + } + tilesToTick.add(registeredTileClass); + } + return tilesToTick; + } +} \ No newline at end of file diff --git a/eSpigot-Server/src/main/java/com/elevatemc/spigot/visuals/CannonTrackerEntry.java b/eSpigot-Server/src/main/java/com/elevatemc/spigot/visuals/CannonTrackerEntry.java new file mode 100644 index 0000000..016d118 --- /dev/null +++ b/eSpigot-Server/src/main/java/com/elevatemc/spigot/visuals/CannonTrackerEntry.java @@ -0,0 +1,111 @@ +package com.elevatemc.spigot.visuals; + +import net.minecraft.server.*; + +import java.util.List; + +/* + * This is a custom entity tracker made for the cannoning entities tnt and sand. + * The goal behind this is to reduce packets and logic without hiding entities. + * It may not completely replicate the original behavior, but it should make up + * for that with it's advantages. + * Source: IonSpigot + */ +public class CannonTrackerEntry extends EntityTrackerEntry { + + private boolean movingX; + private boolean movingY; + private boolean movingZ; + + private double updateX; + private double updateY; + private double updateZ; + + public CannonTrackerEntry(Entity entity, int i, int j, boolean flag) { + super(entity, i, j, flag); + this.movingX = entity.motX != 0.0; + this.movingY = true; + this.movingZ = entity.motZ != 0.0; + this.updateX = entity.locX; + this.updateY = entity.locY; + this.updateZ = entity.locZ; + } + + @Override + public void track(List list) { + boolean motionX = this.tracker.motX != 0.0; + boolean motionY = this.tracker.motY != 0.0; + boolean motionZ = this.tracker.motZ != 0.0; + + // This tracked entities motion has changed or an explosion has occurred, update it! + if (!this.tracker.ai && motionX == movingX && motionY == movingY && motionZ == movingZ) { + return; + } + + // This entity has moved 4 blocks since the last update, search for players + if (this.tracker.e(updateX, updateY, updateZ) > 16.0D) { + this.scanPlayers(list); + this.updateX = this.tracker.locX; + this.updateY = this.tracker.locY; + this.updateZ = this.tracker.locZ; + } + + // Update nearby players, only resynchronise when motion is updated + if (motionX || motionY || motionZ) { + this.broadcastUpdate(); + } + + // Keep what of which axis the entity is moving on + this.tracker.ai = false; + this.movingX = motionX; + this.movingY = motionY; + this.movingZ = motionZ; + } + + private void broadcastUpdate() { + DataWatcher datawatcher = this.tracker.getDataWatcher(); + + if (datawatcher.a()) { + this.broadcastIncludingSelf(new PacketPlayOutEntityMetadata(this.tracker.getId(), datawatcher, false)); + } + + // Only update location on movement + if (this.tracker.lastX != this.tracker.locX || this.tracker.lastY != this.tracker.locY || this.tracker.lastZ != this.tracker.locZ) { + this.broadcast(new PacketPlayOutEntityTeleport(this.tracker)); + } + + this.broadcast(new PacketPlayOutEntityVelocity(this.tracker)); + } + + @Override + public void updatePlayer(EntityPlayer entityplayer) { + // Check configurable distance as a cube then visible distance. + if (this.c(entityplayer) && this.tracker.h(entityplayer) < 4096.0D) { + if (this.trackedPlayers.contains(entityplayer) || (!this.e(entityplayer) && !this.tracker.attachedToPlayer)) { + return; + } + + entityplayer.removeQueue.remove(Integer.valueOf(this.tracker.getId())); + + this.trackedPlayerMap.put(entityplayer, true); // Paper + Packet packet = this.c(); // IonSpigot + if (packet == null) return; // IonSpigot - If it's null don't update the client! + + entityplayer.playerConnection.sendPacket(packet); + + if (this.tracker.getCustomNameVisible()) { + entityplayer.playerConnection.sendPacket(new PacketPlayOutEntityMetadata(this.tracker.getId(), this.tracker.getDataWatcher(), true)); + } + + entityplayer.playerConnection.sendPacket(new PacketPlayOutEntityVelocity(this.tracker.getId(), this.tracker.motX, this.tracker.motY, this.tracker.motZ)); + + if (this.tracker.vehicle != null) { + entityplayer.playerConnection.sendPacket(new PacketPlayOutAttachEntity(0, this.tracker, this.tracker.vehicle)); + } + } else if (this.trackedPlayers.contains(entityplayer)) { + this.trackedPlayers.remove(entityplayer); + entityplayer.d(this.tracker); + } + } + +} \ No newline at end of file diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/Block.java b/eSpigot-Server/src/main/java/net/minecraft/server/Block.java index 34484d8..46b3977 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/Block.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/Block.java @@ -1,6 +1,8 @@ package net.minecraft.server; +import com.elevatemc.spigot.config.eSpigotConfig; import com.elevatemc.spigot.event.BlockDropItemsEvent; +import com.elevatemc.spigot.redstone.PandaRedstoneWire; import org.bukkit.Bukkit; import org.bukkit.craftbukkit.entity.CraftItem; @@ -805,7 +807,7 @@ public class Block { a(52, "mob_spawner", (new BlockMobSpawner()).c(5.0F).a(Block.j).c("mobSpawner").K()); a(53, "oak_stairs", (new BlockStairs(block1.getBlockData().set(BlockWood.VARIANT, BlockWood.EnumLogVariant.OAK))).c("stairsWood")); a(54, "chest", (new BlockChest(0)).c(2.5F).a(Block.f).c("chest")); - a(55, "redstone_wire", (new BlockRedstoneWire()).c(0.0F).a(Block.e).c("redstoneDust").K()); + a(55, "redstone_wire", (eSpigotConfig.pandaWire ? new PandaRedstoneWire() : new BlockRedstoneWire()).c(0.0F).a(Block.e).c("redstoneDust").K()); a(56, "diamond_ore", (new BlockOre()).c(3.0F).b(5.0F).a(Block.i).c("oreDiamond")); a(57, "diamond_block", (new Block(Material.ORE, MaterialMapColor.G)).c(5.0F).b(10.0F).a(Block.j).c("blockDiamond").a(CreativeModeTab.b)); a(58, "crafting_table", (new BlockWorkbench()).c(2.5F).a(Block.f).c("workbench")); diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/BlockFalling.java b/eSpigot-Server/src/main/java/net/minecraft/server/BlockFalling.java index e8e679e..0b43211 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/BlockFalling.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/BlockFalling.java @@ -34,7 +34,7 @@ public class BlockFalling extends Block { if (canFall(world, blockposition.down()) && blockposition.getY() >= 0) { byte b0 = 32; - if (!BlockFalling.instaFall && world.areChunksLoadedBetween(blockposition.a(-b0, -b0, -b0), blockposition.a(b0, b0, b0))) { + if (world.paperSpigotConfig.fixSandUnloading || !BlockFalling.instaFall && world.areChunksLoadedBetween(blockposition.a(-b0, -b0, -b0), blockposition.a(b0, b0, b0))) { if (!world.isClientSide) { // PaperSpigot start - Add FallingBlock source location API org.bukkit.Location loc = new org.bukkit.Location(world.getWorld(), (float) blockposition.getX() + 0.5F, blockposition.getY(), (float) blockposition.getZ() + 0.5F); diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/BlockFlowing.java b/eSpigot-Server/src/main/java/net/minecraft/server/BlockFlowing.java index 1da089f..4261632 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/BlockFlowing.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/BlockFlowing.java @@ -28,6 +28,7 @@ public class BlockFlowing extends BlockFluids { org.bukkit.Server server = world.getServer(); org.bukkit.block.Block source = bworld == null ? null : bworld.getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()); // CraftBukkit end + BlockPosition pos = blockposition.down(); // IonSpigot - Cache Below Position int i = iblockdata.get(BlockFlowing.LEVEL); byte b0 = 1; @@ -35,7 +36,7 @@ public class BlockFlowing extends BlockFluids { b0 = 2; } - int j = this.getFlowSpeed(world, blockposition); // PaperSpigot + // int j = this.getFlowSpeed(world, blockposition); // PaperSpigot int k; if (i > 0) { @@ -43,20 +44,30 @@ public class BlockFlowing extends BlockFluids { this.a = 0; - EnumDirection enumdirection; + // IonSpigot start - Optimise Fluids + int temp = this.e(world, blockposition.up()); + int i1 = b0; + if (temp < 8) { + EnumDirection enumdirection; - for (Iterator iterator = EnumDirection.EnumDirectionLimit.HORIZONTAL.iterator(); iterator.hasNext(); l = this.a(world, blockposition.shift(enumdirection), l)) { - enumdirection = (EnumDirection) iterator.next(); + for (Iterator iterator = EnumDirection.EnumDirectionLimit.HORIZONTAL.iterator(); iterator.hasNext(); l = this.a(world, blockposition.shift(enumdirection), l)) { + enumdirection = (EnumDirection) iterator.next(); + } + + i1 = l + b0; + + if (i1 >= 8 || l < 0) { + i1 = -1; + } + } else if (temp == 8 && world.getType(pos) == Blocks.AIR.getBlockData()) { + // TODO: Look into adding an option to make this 2 + this.f(world, blockposition, iblockdata); + world.setTypeAndData(pos, iblockdata, 3); + return; } - int i1 = l + b0; - - if (i1 >= 8 || l < 0) { - i1 = -1; - } - - if (this.e(world, blockposition.up()) >= 0) { - k = this.e(world, blockposition.up()); + if (temp >= 0) { + k = temp; if (k >= 8) { i1 = k; } else { @@ -65,7 +76,7 @@ public class BlockFlowing extends BlockFluids { } if (this.a >= 2 && this.material == Material.WATER) { - IBlockData iblockdata1 = world.getType(blockposition.down()); + IBlockData iblockdata1 = world.getType(pos); // IonSpigot - Cache Below Position if (iblockdata1.getBlock().getMaterial().isBuildable()) { i1 = 0; @@ -74,9 +85,13 @@ public class BlockFlowing extends BlockFluids { } } - if (!world.paperSpigotConfig.fastDrainLava && this.material == Material.LAVA && i < 8 && i1 < 8 && i1 > i && random.nextInt(4) != 0) { // PaperSpigot - j *= 4; - } + // IonSpigot start - Unused logic + /* + if (!world.paperSpigotConfig.fastDrainLava && this.material == Material.LAVA && i < 8 && i1 < 8 && i1 > i && random.nextInt(4) != 0) { // PaperSpigot + j *= 4; + } + */ + // IonSpigot end if (i1 == i) { this.f(world, blockposition, iblockdata); @@ -85,6 +100,7 @@ public class BlockFlowing extends BlockFluids { if (i1 < 0 || canFastDrain(world, blockposition)) { // PaperSpigot - Fast draining world.setAir(blockposition); } else { + int j = this.getFlowSpeed(world, blockposition); // PaperSpigot // IonSpigot - From above iblockdata = iblockdata.set(BlockFlowing.LEVEL, i1); world.setTypeAndData(blockposition, iblockdata, 2); world.a(blockposition, this, j); @@ -103,9 +119,10 @@ public class BlockFlowing extends BlockFluids { } if (world.getType(blockposition).getBlock().getMaterial() != material) return; // PaperSpigot - Stop updating flowing block if material has changed - IBlockData iblockdata2 = world.getType(blockposition.down()); + // IonSpigot start - Cache Below Position + IBlockData iblockdata2 = world.getType(pos); - if (this.h(world, blockposition.down(), iblockdata2)) { + if (this.h(world, pos, iblockdata2)) { // CraftBukkit start - Send "down" to the server if (!canFlowTo(world, source, BlockFace.DOWN)) { return; } // Paper BlockFromToEvent event = new BlockFromToEvent(source, BlockFace.DOWN); @@ -113,20 +130,21 @@ public class BlockFlowing extends BlockFluids { server.getPluginManager().callEvent(event); } if (!event.isCancelled()) { - if (this.material == Material.LAVA && world.getType(blockposition.down()).getBlock().getMaterial() == Material.WATER) { - world.setTypeUpdate(blockposition.down(), Blocks.STONE.getBlockData()); - this.fizz(world, blockposition.down()); + if (this.material == Material.LAVA && world.getType(pos).getBlock().getMaterial() == Material.WATER) { + world.setTypeUpdate(pos, Blocks.STONE.getBlockData()); + this.fizz(world, pos); return; } if (i >= 8) { - this.flow(world, blockposition.down(), iblockdata2, i); + this.flow(world, pos, iblockdata2, i); } else { - this.flow(world, blockposition.down(), iblockdata2, i + 8); + this.flow(world, pos, iblockdata2, i + 8); } } // CraftBukkit end - } else if (i >= 0 && (i == 0 || this.g(world, blockposition.down(), iblockdata2))) { + } else if (i >= 0 && (i == 0 || this.g(world, pos, iblockdata2))) { + // IonSpigot end Set set = this.f(world, blockposition); k = i + b0; @@ -150,7 +168,7 @@ public class BlockFlowing extends BlockFluids { } if (!event.isCancelled()) { - this.flow(world, blockposition.shift(enumdirection1), world.getType(blockposition.shift(enumdirection1)), k); + this.flow(world, pos = blockposition.shift(enumdirection1), world.getType(pos), k); // IonSpigot - Don't shift twice } } // CraftBukkit end @@ -188,7 +206,7 @@ public class BlockFlowing extends BlockFluids { BlockPosition blockposition1 = blockposition.shift(enumdirection1); IBlockData iblockdata = world.getType(blockposition1); - if (!this.g(world, blockposition1, iblockdata) && (iblockdata.getBlock().getMaterial() != this.material || iblockdata.get(BlockFlowing.LEVEL) > 0)) { + if (!this.g(iblockdata.getBlock()) && (iblockdata.getBlock().getMaterial() != this.material || iblockdata.get(BlockFlowing.LEVEL) > 0)) { // IonSpigot - Optimise Fluids if (!this.g(world, blockposition1.down(), iblockdata)) { return i; } @@ -215,11 +233,11 @@ public class BlockFlowing extends BlockFluids { BlockPosition blockposition1 = blockposition.shift(enumdirection); IBlockData iblockdata = world.getType(blockposition1); - if (!this.g(world, blockposition1, iblockdata) && (iblockdata.getBlock().getMaterial() != this.material || iblockdata.get(BlockFlowing.LEVEL) > 0)) { + if (!this.g(iblockdata.getBlock()) && (iblockdata.getBlock().getMaterial() != this.material || iblockdata.get(BlockFlowing.LEVEL) > 0)) { // IonSpigot - Optimise Fluids int j; - if (this.g(world, blockposition1.down(), world.getType(blockposition1.down()))) { - j = this.a(world, blockposition1, 1, enumdirection.opposite()); + if (this.g(world, blockposition1.down(), iblockdata)) { // IonSpigot - Unused check + j = this.a(world, blockposition1, 4, enumdirection.opposite()); // IonSpigot - Optimise Fluids } else { j = 0; } @@ -240,7 +258,11 @@ public class BlockFlowing extends BlockFluids { private boolean g(World world, BlockPosition blockposition, IBlockData iblockdata) { Block block = world.getType(blockposition).getBlock(); - + // IonSpigot start - Optimise Fluids + return this.g(block); + } + private boolean g(Block block) { + // IonSpigot end return block instanceof BlockDoor || block == Blocks.STANDING_SIGN || block == Blocks.LADDER || block == Blocks.REEDS || (block.material == Material.PORTAL || block.material.isSolid()); } @@ -265,7 +287,7 @@ public class BlockFlowing extends BlockFluids { private boolean h(World world, BlockPosition blockposition, IBlockData iblockdata) { Material material = iblockdata.getBlock().getMaterial(); - return material != this.material && material != Material.LAVA && !this.g(world, blockposition, iblockdata); + return material != this.material && material != Material.LAVA && !this.g(iblockdata.getBlock()); // IonSpigot - Optimise Fluids } public void onPlace(World world, BlockPosition blockposition, IBlockData iblockdata) { @@ -296,7 +318,16 @@ public class BlockFlowing extends BlockFluids { * PaperSpigot - Data check method for fast draining */ public int getData(World world, BlockPosition position) { - int data = this.e(world, position); + // IonSpigot start - Optimise Draining + return getData(world.getType(position)); + } + public boolean checkData(World world, BlockPosition position, Material material, int data) { + IBlockData state = world.getType(position); + return state.getBlock().getMaterial() == material && getData(state) < data; + } + public int getData(IBlockData iblockdata) { + int data = this.e(iblockdata); + // IonSpigot end return data < 8 ? data : 0; } @@ -311,13 +342,15 @@ public class BlockFlowing extends BlockFluids { result = true; if (getData(world, position.down()) < 0) { result = false; - } else if (world.getType(position.north()).getBlock().getMaterial() == Material.WATER && getData(world, position.north()) < data) { + // IonSpigot start - Optimise Draining + } else if (checkData(world, position.north(), Material.WATER, data)) { result = false; - } else if (world.getType(position.south()).getBlock().getMaterial() == Material.WATER && getData(world, position.south()) < data) { + } else if (checkData(world, position.south(), Material.WATER, data)) { result = false; - } else if (world.getType(position.west()).getBlock().getMaterial() == Material.WATER && getData(world, position.west()) < data) { + } else if (checkData(world, position.west(), Material.WATER, data)) { result = false; - } else if (world.getType(position.east()).getBlock().getMaterial() == Material.WATER && getData(world, position.east()) < data) { + } else if (checkData(world, position.east(), Material.WATER, data)) { + // Ionspigot end result = false; } } @@ -326,13 +359,15 @@ public class BlockFlowing extends BlockFluids { result = true; if (getData(world, position.down()) < 0 || world.getType(position.up()).getBlock().getMaterial() != Material.AIR) { result = false; - } else if (world.getType(position.north()).getBlock().getMaterial() == Material.LAVA && getData(world, position.north()) < data) { + // IonSpigot start - Optimise Draining + } else if (checkData(world, position.north(), Material.LAVA, data)) { result = false; - } else if (world.getType(position.south()).getBlock().getMaterial() == Material.LAVA && getData(world, position.south()) < data) { + } else if (checkData(world, position.south(), Material.LAVA, data)) { result = false; - } else if (world.getType(position.west()).getBlock().getMaterial() == Material.LAVA && getData(world, position.west()) < data) { + } else if (checkData(world, position.west(), Material.LAVA, data)) { result = false; - } else if (world.getType(position.east()).getBlock().getMaterial() == Material.LAVA && getData(world, position.east()) < data) { + } else if (checkData(world, position.east(), Material.LAVA, data)) { + // IonSpigot end result = false; } } diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/BlockFluids.java b/eSpigot-Server/src/main/java/net/minecraft/server/BlockFluids.java index a7df73f..e091aee 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/BlockFluids.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/BlockFluids.java @@ -30,7 +30,12 @@ public abstract class BlockFluids extends Block { } protected int e(IBlockAccess iblockaccess, BlockPosition blockposition) { - return iblockaccess.getType(blockposition).getBlock().getMaterial() == this.material ? iblockaccess.getType(blockposition).get(BlockFluids.LEVEL) : -1; + // IonSpigot start - Optimise Draining + return e(iblockaccess.getType(blockposition)); + } + protected int e(IBlockData iblockdata) { + return iblockdata.getBlock().getMaterial() == this.material ? iblockdata.get(BlockFluids.LEVEL) : -1; + // IonSpigot end } protected int f(IBlockAccess iblockaccess, BlockPosition blockposition) { @@ -172,10 +177,13 @@ public abstract class BlockFluids extends Block { world.makeSound(d0 + 0.5D, d1 + 0.5D, d2 + 0.5D, "random.fizz", 0.5F, 2.6F + (world.random.nextFloat() - world.random.nextFloat()) * 0.8F); - for (int i = 0; i < 8; ++i) { - world.addParticle(EnumParticle.SMOKE_LARGE, d0 + Math.random(), d1 + 1.2D, d2 + Math.random(), 0.0D, 0.0D, 0.0D, Constants.EMPTY_ARRAY); - } - + // IonSpigot start - Remove unused code + /* + for (int i = 0; i < 8; ++i) { + world.addParticle(EnumParticle.SMOKE_LARGE, d0 + Math.random(), d1 + 1.2D, d2 + Math.random(), 0.0D, 0.0D, 0.0D, new int[0]); + } + */ + // IonSpigot end } public IBlockData fromLegacyData(int i) { diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/BlockHopper.java b/eSpigot-Server/src/main/java/net/minecraft/server/BlockHopper.java index 28dd4e6..e56642b 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/BlockHopper.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/BlockHopper.java @@ -28,19 +28,23 @@ public class BlockHopper extends BlockContainer { } public void a(World world, BlockPosition blockposition, IBlockData iblockdata, AxisAlignedBB axisalignedbb, List list, Entity entity) { + // North side + this.a(0.0F, 0.625F, 0.125F, 1.0F, 1.0F, 0.0F); + super.a(world, blockposition, iblockdata, axisalignedbb, list, entity); + // South side + this.a(0.0F, 0.625F, 0.875F, 1.0F, 1.0F, 1.0F); + super.a(world, blockposition, iblockdata, axisalignedbb, list, entity); + // West side + this.a(0.125F, 0.625F, 0.0F, 0.0F, 1.0F, 1.0F); + super.a(world, blockposition, iblockdata, axisalignedbb, list, entity); + // East side + this.a(0.875F, 0.625F, 0.0F, 1.0F, 1.0F, 1.0F); + super.a(world, blockposition, iblockdata, axisalignedbb, list, entity); + // Bottom part this.a(0.0F, 0.0F, 0.0F, 1.0F, 0.625F, 1.0F); super.a(world, blockposition, iblockdata, axisalignedbb, list, entity); - float f = 0.125F; - - this.a(0.0F, 0.0F, 0.0F, f, 1.0F, 1.0F); - super.a(world, blockposition, iblockdata, axisalignedbb, list, entity); - this.a(0.0F, 0.0F, 0.0F, 1.0F, 1.0F, f); - super.a(world, blockposition, iblockdata, axisalignedbb, list, entity); - this.a(1.0F - f, 0.0F, 0.0F, 1.0F, 1.0F, 1.0F); - super.a(world, blockposition, iblockdata, axisalignedbb, list, entity); - this.a(0.0F, 0.0F, 1.0F - f, 1.0F, 1.0F, 1.0F); - super.a(world, blockposition, iblockdata, axisalignedbb, list, entity); - this.a(0.0F, 0.0F, 0.0F, 1.0F, 1.0F, 1.0F); + // Reset + this.updateShape(world, blockposition); } public IBlockData getPlacedState(World world, BlockPosition blockposition, EnumDirection enumdirection, float f, float f1, float f2, int i, EntityLiving entityliving) { diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/Chunk.java b/eSpigot-Server/src/main/java/net/minecraft/server/Chunk.java index 5bf4ec2..fa68764 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/Chunk.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/Chunk.java @@ -1,5 +1,6 @@ package net.minecraft.server; +import com.elevatemc.spigot.util.ObjectMapList; import com.google.common.base.Predicate; import com.google.common.collect.Maps; import com.google.common.collect.Queues; @@ -163,7 +164,7 @@ public class Chunk { this.heightMap = new int[256]; for (int k = 0; k < this.entitySlices.length; ++k) { - this.entitySlices[k] = new org.bukkit.craftbukkit.util.UnsafeList(); // Spigot + this.entitySlices[k] = new ObjectMapList<>(); // IonSpigot - UnsafeList -> ObjectMapList // Spigot } Arrays.fill(this.f, -999); @@ -1034,6 +1035,58 @@ public class Chunk { this.q = true; } + // IonSpigot start - Optimise Entity Collisions + public boolean collectEntitiesByAmount(Entity source, AxisAlignedBB axisalignedbb, List entities, + Predicate by, int amount) { + int i = MathHelper.floor((axisalignedbb.b - 2.0D) / 16.0D); + int j = MathHelper.floor((axisalignedbb.e + 2.0D) / 16.0D); + + i = MathHelper.clamp(i, 0, this.entitySlices.length - 1); + j = MathHelper.clamp(j, 0, this.entitySlices.length - 1); + + for (int k = i; k <= j; ++k) { + if (!this.entitySlices[k].isEmpty()) { + if (collectEntities(source, axisalignedbb, this.entitySlices[k], entities, by, amount)) { + return true; + } + } + } + + return false; + } + + private boolean collectEntities(Entity source, AxisAlignedBB axisalignedbb, List slice, + List entities, Predicate by, int amount) { + for (int i = 0; i < slice.size(); ++i) { + int next = world.random.nextInt(slice.size()); + Entity entity = slice.get(next); + + if (entity.getBoundingBox().b(axisalignedbb) && entity != source && !entities.contains(entity)) { + if (by == null || by.apply(entity)) { + entities.add(entity); + } + + Entity[] passengers = entity.aB(); + + if (passengers != null) { + for (Entity value : passengers) { + if (value != source && value.getBoundingBox().b(axisalignedbb) && (by == null + || by.apply(value)) && !entities.contains(entity)) { + entities.add(value); + } + } + } + + if (entities.size() >= amount) { + return true; + } + } + } + + return false; + } + // IonSpigot end + public void a(Entity entity, AxisAlignedBB axisalignedbb, List list, Predicate predicate) { int i = MathHelper.floor((axisalignedbb.b - 2.0D) / 16.0D); int j = MathHelper.floor((axisalignedbb.e + 2.0D) / 16.0D); @@ -1087,7 +1140,7 @@ public class Chunk { // PaperSpigot start int[] counts; - if (ItemStack.class.isAssignableFrom(oclass)) { + if (EntityItem.class.isAssignableFrom(oclass)) { counts = itemCounts; } else if (IInventory.class.isAssignableFrom(oclass)) { counts = inventoryEntityCounts; diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/ChunkProviderServer.java b/eSpigot-Server/src/main/java/net/minecraft/server/ChunkProviderServer.java index f8709ba..ef45ef5 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/ChunkProviderServer.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/ChunkProviderServer.java @@ -23,7 +23,7 @@ import org.bukkit.event.world.ChunkUnloadEvent; public class ChunkProviderServer implements IChunkProvider { private static final Logger b = LogManager.getLogger(); - public LongSet unloadQueue = new LongOpenHashSet(20); // CraftBukkit - LongHashSet // TacoSpigot - LongHashSet -> HashArraySet + public LongSet unloadQueue = new LongOpenHashSet(); // CraftBukkit - LongHashSet // TacoSpigot - LongHashSet -> HashArraySet // IonSpigot - LongOpenHashSet public Chunk emptyChunk; public IChunkProvider chunkProvider; public IChunkLoader chunkLoader; // KigPaper - private -> public @@ -69,8 +69,9 @@ public class ChunkProviderServer implements IChunkProvider { } public void queueUnload(int i, int j) { + long key = LongHash.toLong(i, j); // IonSpigot - Only create key once // PaperSpigot start - Asynchronous lighting updates - Chunk chunk = chunks.get(LongHash.toLong(i, j)); + Chunk chunk = chunks.get(key); // IonSpigot if (chunk != null && chunk.world.paperSpigotConfig.useAsyncLighting && (chunk.pendingLightUpdates.get() > 0 || chunk.world.getTime() - chunk.lightUpdateTime < 20)) { return; } @@ -89,9 +90,9 @@ public class ChunkProviderServer implements IChunkProvider { if (this.world.worldProvider.e()) { if (!this.world.c(i, j)) { // CraftBukkit start - this.unloadQueue.add(LongHash.toLong(i, j)); // TacoSpigot - directly invoke LongHash + this.unloadQueue.add(key); // TacoSpigot - directly invoke LongHash - Chunk c = chunks.get(LongHash.toLong(i, j)); + Chunk c = chunks.get(key); if (c != null) { c.mustSave = true; } @@ -99,9 +100,9 @@ public class ChunkProviderServer implements IChunkProvider { } } else { // CraftBukkit start - this.unloadQueue.add(LongHash.toLong(i, j)); // TacoSpigot - directly invoke LongHash + this.unloadQueue.add(key); // TacoSpigot - directly invoke LongHash - Chunk c = chunks.get(LongHash.toLong(i, j)); + Chunk c = chunks.get(key); if (c != null) { c.mustSave = true; } @@ -128,7 +129,8 @@ public class ChunkProviderServer implements IChunkProvider { } public Chunk getChunkAt(int i, int j, Runnable runnable) { - Chunk chunk = chunks.get(LongHash.toLong(i, j)); + long key = LongHash.toLong(i, j); // IonSpigot - Only create key once + Chunk chunk = chunks.get(key); // IonSpigot ChunkRegionLoader loader = null; if (this.chunkLoader instanceof ChunkRegionLoader) { @@ -147,7 +149,7 @@ public class ChunkProviderServer implements IChunkProvider { chunk = originalGetChunkAt(i, j); } - unloadQueue.remove(LongHash.toLong(i, j)); // SportPaper + unloadQueue.remove(key); // SportPaper // If we didn't load the chunk async and have a callback run it now if (runnable != null) { runnable.run(); @@ -156,7 +158,8 @@ public class ChunkProviderServer implements IChunkProvider { return chunk; } public Chunk originalGetChunkAt(int i, int j) { - Chunk chunk = this.chunks.get(LongHash.toLong(i, j)); + long key = LongHash.toLong(i, j); // IonSpigot - Only create key once + Chunk chunk = this.chunks.get(key); boolean newChunk = false; // CraftBukkit end @@ -173,8 +176,8 @@ public class ChunkProviderServer implements IChunkProvider { CrashReport crashreport = CrashReport.a(throwable, "Exception generating new chunk"); CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Chunk to be generated"); - crashreportsystemdetails.a("Location", String.format("%d,%d", new Object[] {i, j})); - crashreportsystemdetails.a("Position hash", LongHash.toLong(i, j)); // CraftBukkit - Use LongHash + crashreportsystemdetails.a("Location", String.format("%d,%d", i, j)); + crashreportsystemdetails.a("Position hash", key); // CraftBukkit - Use LongHash crashreportsystemdetails.a("Generator", this.chunkProvider.getName()); throw new ReportedException(crashreport); } @@ -182,7 +185,7 @@ public class ChunkProviderServer implements IChunkProvider { newChunk = true; // CraftBukkit } - this.chunks.put(LongHash.toLong(i, j), chunk); + this.chunks.put(key, chunk); chunk.addEntities(); @@ -216,16 +219,31 @@ public class ChunkProviderServer implements IChunkProvider { world.timings.syncChunkLoadTimer.stopTiming(); // Spigot } - this.unloadQueue.remove(LongHash.toLong(i, j)); // SportPaper + this.unloadQueue.remove(key); // SportPaper return chunk; } + // IonSpigot start - Optimise Chunk Getting + private Chunk cachedChunk = null; public Chunk getOrCreateChunk(int i, int j) { + Chunk chunk = cachedChunk; // We have to do this for thread safety + if (chunk != null && chunk.locX == i && chunk.locZ == j && chunk.o()) { + return chunk; + } // CraftBukkit start - Chunk chunk = this.chunks.get(LongHash.toLong(i, j)); + chunk = this.chunks.get(LongHash.toLong(i, j)); - chunk = chunk == null ? (!this.world.ad() && !this.forceChunkLoad ? this.emptyChunk : this.getChunkAt(i, j)) : chunk; + if (chunk == null) { + if (!this.world.ad() && !this.forceChunkLoad) { + return this.emptyChunk; + } + chunk = this.getChunkAt(i, j); + } + + cachedChunk = chunk; + + /* if (chunk == emptyChunk) return chunk; if (i != chunk.locX || j != chunk.locZ) { b.error("Chunk (" + chunk.locX + ", " + chunk.locZ + ") stored at (" + i + ", " + j + ") in world '" + world.getWorld().getName() + "'"); @@ -233,7 +251,8 @@ public class ChunkProviderServer implements IChunkProvider { Throwable ex = new Throwable(); ex.fillInStackTrace(); ex.printStackTrace(); - } + } */ + // IonSpigot end return chunk; // CraftBukkit end diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/ChunkSection.java b/eSpigot-Server/src/main/java/net/minecraft/server/ChunkSection.java index a4e179d..e9774b0 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/ChunkSection.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/ChunkSection.java @@ -73,7 +73,10 @@ public class ChunkSection { } public boolean a() { - return this.nonEmptyBlockCount == 0; + //return this.nonEmptyBlockCount == 0; + // Paper - MC-80966 + // Even if there are no blocks, there may be other information associated with the chunk, always send it. + return false; } public boolean shouldTick() { diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/DedicatedServer.java b/eSpigot-Server/src/main/java/net/minecraft/server/DedicatedServer.java index 90a71bd..fd43766 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/DedicatedServer.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/DedicatedServer.java @@ -196,7 +196,7 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer if (!org.spigotmc.SpigotConfig.lateBind) { try { - this.aq().bind(bindAddress); // PandaSpigot - Unix domain socket support + this.aq().a(bindAddress, this.R()); // PandaSpigot - Unix domain socket support } catch (IOException ioexception) { DedicatedServer.LOGGER.warn("**** FAILED TO BIND TO PORT!"); DedicatedServer.LOGGER.warn("The exception was: {}", new Object[] { ioexception.toString()}); @@ -292,7 +292,7 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer if (org.spigotmc.SpigotConfig.lateBind) { try { - this.aq().bind(bindAddress); // PandaSpigot - Unix domain socket support + this.aq().a(bindAddress, this.R()); // PandaSpigot - Unix domain socket support } catch (IOException ioexception) { DedicatedServer.LOGGER.warn("**** FAILED TO BIND TO PORT!"); DedicatedServer.LOGGER.warn("The exception was: {}", new Object[] { ioexception.toString()}); @@ -434,7 +434,15 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer } public boolean ai() { - return this.propertyManager.getBoolean("use-native-transport", true); + return org.apache.commons.lang.SystemUtils.IS_OS_LINUX && this.getTransport() == ServerConnection.EventGroupType.EPOLL; // Nacho - Add a check to see if we are using Linux or not, if not ignore this. + } + + public ServerConnection.EventGroupType getTransport() { + try { + return ServerConnection.EventGroupType.valueOf(this.propertyManager.getString("transport-to-use", "default").toUpperCase()); + } catch (Exception ignored) { + return ServerConnection.EventGroupType.DEFAULT; + } } public DedicatedPlayerList aP() { diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/Entity.java b/eSpigot-Server/src/main/java/net/minecraft/server/Entity.java index f9de1d9..3a05d17 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/Entity.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/Entity.java @@ -134,8 +134,13 @@ public abstract class Entity implements ICommandListener { public boolean valid; // CraftBukkit public org.bukkit.projectiles.ProjectileSource projectileSource; // CraftBukkit - For projectiles only public boolean forceExplosionKnockback; // CraftBukkit - SPIGOT-949 + public boolean isCannoningEntity; // IonSpigot public boolean inUnloadedChunk = false; // PaperSpigot - Remove entities in unloaded chunks public boolean loadChunks = false; // PaperSpigot - Entities can load chunks they move through and keep them loaded + // IonSpigot start - Lag Compensated Ticking + protected boolean compensated; + protected void tick() {} + // IonSpigot end public static Random SHARED_RANDOM = new FastRandom() { private boolean locked = false; @@ -206,6 +211,7 @@ public abstract class Entity implements ICommandListener { this.defaultActivationState = false; } // Spigot end + this.isCannoningEntity = this instanceof EntityTNTPrimed || this instanceof EntityFallingBlock; // IonSpigot this.datawatcher = new DataWatcher(this); this.datawatcher.a(0, (byte) 0); @@ -455,15 +461,36 @@ public abstract class Entity implements ICommandListener { * PaperSpigot - Load surrounding chunks the entity is moving through */ public void loadChunks() { + // IonSpigot start - Fix Load Chunks + /* + This implementation is flawed, as it does not work properly in south and east directions. + The reason for this is because the motion would be negative in those directions which + would cause the checks to fail, as it is missing min and max checks. + Now you're going to be saying my cannon loaded chunks in those directions, + you are right about that, the reason it works is because theres an additional layer + of chunk loading, I personally believe this method is meant to ensure that the current position + is loaded as it isn't guaranteed that the entity will move at all. + This additional layer is located in the getCubes method, we can remove the excess logic from here + and take advantage of that with the triangle patch that was implemented in TacoSpigot + this allows for triangle chunk loading allowing to us to reduce chunks loaded by cannons. for (int cx = (int) locX >> 4; cx <= (int) (locX + motX) >> 4; ++cx) { for (int cz = (int) locZ >> 4; cz <= (int) (locZ + motZ) >> 4; ++cz) { ((ChunkProviderServer) world.chunkProvider).getChunkAt(cx, cz); } } + */ + int chunkX = org.bukkit.util.NumberConversions.floor(locX) >> 4; + int chunkZ = org.bukkit.util.NumberConversions.floor(locZ) >> 4; + ((ChunkProviderServer) world.chunkProvider).getChunkAt(chunkX, chunkZ); + // IonSpigot end } public void move(double d0, double d1, double d2) { + // Check if we're moving + if (d0 == 0 && d1 == 0 && d2 == 0 && this.vehicle == null && this.passenger == null) { + return; + } if (this.loadChunks) loadChunks(); // PaperSpigot - Load chunks // FlamePaper start - Disable Unloaded Chunk Movement if (!world.chunkProvider.isChunkLoaded((int) locX >> 4, (int) locZ >> 4)) { @@ -486,10 +513,6 @@ public abstract class Entity implements ICommandListener { this.appendEntityCrashDetails(crashreportsystemdetails); throw new ReportedException(crashreport); } - // Check if we're moving - if (d0 == 0 && d1 == 0 && d2 == 0 && this.vehicle == null && this.passenger == null) { - return; - } // CraftBukkit end this.world.methodProfiler.a("move"); double d3 = this.locX; @@ -1318,6 +1341,7 @@ public abstract class Entity implements ICommandListener { public void b(Entity entity, int i) {} public boolean c(NBTTagCompound nbttagcompound) { + if (this instanceof EntityFireworks || this instanceof EntityArrow)return false; // YAPFA - Don't save arrows or fireworks String s = this.ag(); if (!this.dead && s != null) { diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/EntityBat.java b/eSpigot-Server/src/main/java/net/minecraft/server/EntityBat.java index 5241620..77b81f3 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/EntityBat.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/EntityBat.java @@ -163,7 +163,7 @@ public class EntityBat extends EntityAmbient { if (blockposition.getY() >= this.world.F()) { return false; } else { - int i = this.world.getLightLevel(blockposition); + //int i = this.world.getLightLevel(blockposition); // Yatopia - moved down byte b0 = 4; if (this.a(this.world.Y())) { @@ -172,6 +172,7 @@ public class EntityBat extends EntityAmbient { return false; } + int i = this.world.getLightLevel(blockposition); // Yatopia - moved from above return i > this.random.nextInt(b0) ? false : super.bR(); } } diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/EntityEnderPearl.java b/eSpigot-Server/src/main/java/net/minecraft/server/EntityEnderPearl.java index 682b889..8edf9c1 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/EntityEnderPearl.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/EntityEnderPearl.java @@ -1,6 +1,7 @@ package net.minecraft.server; // CraftBukkit start +import com.elevatemc.spigot.config.eSpigotConfig; import com.elevatemc.spigot.event.PlayerPearlRefundEvent; import com.google.common.collect.Sets; import org.bukkit.Bukkit; @@ -41,6 +42,12 @@ public class EntityEnderPearl extends EntityProjectile { super(world, entityliving); this.c = entityliving; this.loadChunks = world.paperSpigotConfig.loadUnloadedEnderPearls; // PaperSpigot + // IonSpigot start - Lag Compensated Pearls + if (entityliving instanceof EntityPlayer && eSpigotConfig.lagCompensatedPearls) { + ((EntityPlayer) entityliving).lagCompensatedTicking.add(this); + compensated = true; + } + // IonSpigot end } @Override @@ -152,8 +159,15 @@ public class EntityEnderPearl extends EntityProjectile { } } + // IonSpigot start - Lag Compensated Pearls @Override public void t_() { + if (!compensated) { + tick(); + } + } + public void tick() { + // IonSpigot end final EntityLiving shooter = this.getShooter(); // ImHacking Start - Prevent Pearling into blocks diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/EntityHuman.java b/eSpigot-Server/src/main/java/net/minecraft/server/EntityHuman.java index 54ee9d2..aa9c645 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/EntityHuman.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/EntityHuman.java @@ -1376,7 +1376,7 @@ public abstract class EntityHuman extends EntityLiving { } public void b(Statistic statistic) { - this.a(statistic, 1); + this.a(statistic, 20); } public void a(Statistic statistic, int i) { diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/EntityLiving.java b/eSpigot-Server/src/main/java/net/minecraft/server/EntityLiving.java index fb9ff97..2bb8d3e 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/EntityLiving.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/EntityLiving.java @@ -108,6 +108,7 @@ public abstract class EntityLiving extends Entity { ++this.ticksFarFromPlayer; // Above all the floats } // Spigot end + private int tick; public void G() { this.damageEntity(DamageSource.OUT_OF_WORLD, Float.MAX_VALUE); @@ -1508,11 +1509,12 @@ public abstract class EntityLiving extends Entity { } } + tick++; for (int j = 0; j < 5; ++j) { ItemStack itemstack = this.h[j]; ItemStack itemstack1 = this.getEquipment(j); - if (!ItemStack.matches(itemstack1, itemstack)) { + if (!ItemStack.fastMatches(itemstack1, itemstack) || (this.tick % 20 == 0 && !ItemStack.matches(itemstack1, itemstack))) { ((WorldServer) this.world).getTracker().a(this, new PacketPlayOutEntityEquipment(this.getId(), j, itemstack1)); if (itemstack != null) { this.c.a(itemstack.B()); @@ -1709,15 +1711,10 @@ public abstract class EntityLiving extends Entity { if (!eSpigotConfig.entityCollisions) return; - List list = this.world.a(this, this.getBoundingBox().grow(0.20000000298023224D, 0.0D, 0.20000000298023224D), Predicates.and(IEntitySelector.d, new Predicate() { - public boolean a(Entity entity) { - return entity.ae(); - } - - public boolean apply(Object object) { - return this.a((Entity) object); - } - })); + // IonSpigot start - Optimise Entity Collisions + List list = this.world.getEntitiesByAmount(this, this.getBoundingBox().grow(0.20000000298023224D, 0.0D, 0.20000000298023224D), + input -> IEntitySelector.d.apply(input) && input != null && input.ae(), world.spigotConfig.maxCollisionsPerEntity); + // IonSpigot end if (this.ad() && !list.isEmpty()) { // Spigot: Add this.ad() condition numCollisions -= world.spigotConfig.maxCollisionsPerEntity; // Spigot diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/EntityMinecartAbstract.java b/eSpigot-Server/src/main/java/net/minecraft/server/EntityMinecartAbstract.java index 78c37e9..7497f7b 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/EntityMinecartAbstract.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/EntityMinecartAbstract.java @@ -30,9 +30,9 @@ public abstract class EntityMinecartAbstract extends Entity implements INamableT private double derailedX = 0.5; private double derailedY = 0.5; private double derailedZ = 0.5; - private double flyingX = 0.95; - private double flyingY = 0.95; - private double flyingZ = 0.95; + private double flyingX = 0.949999988079071D; // Paper - restore vanilla precision + private double flyingY = 0.949999988079071D; // Paper - restore vanilla precision + private double flyingZ = 0.949999988079071D; // Paper - restore vanilla precision public double maxSpeed = 0.4D; // CraftBukkit end diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/EntityPlayer.java b/eSpigot-Server/src/main/java/net/minecraft/server/EntityPlayer.java index 0eab50f..7ed3ded 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/EntityPlayer.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/EntityPlayer.java @@ -51,6 +51,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { public boolean g; public int ping; public boolean viewingCredits; + public List lagCompensatedTicking = new ArrayList<>(); // IonSpigot - Lag Compensated Ticking // CraftBukkit start public String displayName; @@ -66,7 +67,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { // Spigot start public boolean collidesWithEntities = true; public int viewDistance; // PaperSpigot - Player view distance API - private int containerUpdateDelay; // PaperSpigot + /* private int containerUpdateDelay; */ // PaperSpigot public boolean positiveXMovement, positiveYMovement, positiveZMovement; public KnockbackProfile knockbackProfile = eSpigot.getInstance().getKnockbackHandler().getActiveProfile(); @@ -209,11 +210,11 @@ public class EntityPlayer extends EntityHuman implements ICrafting { if (this.noDamageTicks > 0) { --this.noDamageTicks; } - + // PaperSpigot start - Configurable container update tick rate - if (--containerUpdateDelay <= 0) { + if (/*--containerUpdateDelay <= 0*/ true) { this.activeContainer.b(); - containerUpdateDelay = world.paperSpigotConfig.containerUpdateTickRate; + /*containerUpdateDelay = world.paperSpigotConfig.containerUpdateTickRate;*/ } // PaperSpigot end if (!this.world.isClientSide && !this.activeContainer.a(this)) { @@ -325,6 +326,21 @@ public class EntityPlayer extends EntityHuman implements ICrafting { public void l() { try { super.t_(); + // IonSpigot start - Lag Compensated Ticking + for (int i = 0; i < this.lagCompensatedTicking.size(); ++i) { + Entity entity = this.lagCompensatedTicking.get(i); + entity.tick(); + + // Check if size is > 9, this should cover some abuse + if (entity.dead || this.lagCompensatedTicking.size() > 9) { + if (!entity.dead) { + entity.compensated = false; + } + + this.lagCompensatedTicking.remove(i--); + } + } + // IonSpigot end for (int i = 0; i < this.inventory.getSize(); ++i) { ItemStack itemstack = this.inventory.getItem(i); diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/EntityPotion.java b/eSpigot-Server/src/main/java/net/minecraft/server/EntityPotion.java index e255204..c84a1c8 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/EntityPotion.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/EntityPotion.java @@ -6,12 +6,14 @@ import java.util.List; // CraftBukkit start import java.util.HashMap; +import com.elevatemc.spigot.config.eSpigotConfig; import org.bukkit.craftbukkit.entity.CraftLivingEntity; import org.bukkit.entity.LivingEntity; // CraftBukkit end public class EntityPotion extends EntityProjectile { + public boolean compensated = false; // IonSpigot - Lag Compensated Potions public ItemStack item; public EntityPotion(World world) { @@ -25,6 +27,12 @@ public class EntityPotion extends EntityProjectile { public EntityPotion(World world, EntityLiving entityliving, ItemStack itemstack) { super(world, entityliving); this.item = itemstack; + // IonSpigot start - Lag Compensated Potions + if (entityliving instanceof EntityPlayer && eSpigotConfig.lagCompensatedPotions) { + ((EntityPlayer) entityliving).lagCompensatedTicking.add(this); + compensated = true; + } + // IonSpigot end } public EntityPotion(World world, double d0, double d1, double d2, ItemStack itemstack) { @@ -66,6 +74,18 @@ public class EntityPotion extends EntityProjectile { return this.item.getData(); } + // IonSpigot start - Lag Compensated Potions + @Override + public void t_() { + if (!compensated) { + tick(); + } + } + public void tick() { + super.t_(); + } + // IonSpigot end + protected void a(MovingObjectPosition movingobjectposition) { if (!this.world.isClientSide) { List list = Items.POTION.h(this.item); diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/EntityTNTPrimed.java b/eSpigot-Server/src/main/java/net/minecraft/server/EntityTNTPrimed.java index 4b82987..1f1d642 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/EntityTNTPrimed.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/EntityTNTPrimed.java @@ -52,7 +52,7 @@ public class EntityTNTPrimed extends Entity { } public void t_() { - if (world.spigotConfig.currentPrimedTnt++ > world.spigotConfig.maxTntTicksPerTick) { return; } // Spigot + if (world.spigotConfig.maxTntTicksPerTick > -1 && world.spigotConfig.currentPrimedTnt++ > world.spigotConfig.maxTntTicksPerTick) { return; } // Spigo this.lastX = this.locX; this.lastY = this.locY; this.lastZ = this.locZ; @@ -181,6 +181,8 @@ public class EntityTNTPrimed extends Entity { public boolean W() { if (!world.paperSpigotConfig.fixCannons) return super.W(); + // IonSpigot start - Optimise TNT Ticking + /* // Preserve velocity while calling the super method double oldMotX = this.motX; double oldMotY = this.motY; @@ -208,6 +210,7 @@ public class EntityTNTPrimed extends Entity { } } } + */ return this.inWater; } diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/EntityTracker.java b/eSpigot-Server/src/main/java/net/minecraft/server/EntityTracker.java index 95bb657..62d23bc 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/EntityTracker.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/EntityTracker.java @@ -1,5 +1,7 @@ package net.minecraft.server; +import com.elevatemc.spigot.config.eSpigotConfig; +import com.elevatemc.spigot.visuals.CannonTrackerEntry; import com.google.common.collect.Lists; import com.google.common.collect.Sets; @@ -107,7 +109,7 @@ public class EntityTracker { final int finalI = i; // CraftBukkit - fix decompile error - EntityTrackerEntry entitytrackerentry = new EntityTrackerEntry(entity, finalI, j, flag); + EntityTrackerEntry entitytrackerentry = createTracker(entity, i, j, flag); // IonSpigot // We want to add them to these collections directly to ensure compatibility with plugins like Citizens @@ -153,6 +155,16 @@ public class EntityTracker { }); } + // IonSpigot start + private EntityTrackerEntry createTracker(Entity entity, int i, int j, boolean flag) { + if (entity.isCannoningEntity && eSpigotConfig.cannonTracker) { + return new CannonTrackerEntry(entity, i, j, flag); + } else { + return new EntityTrackerEntry(entity, i, j, flag); + } + } + // IonSpigot end + public void untrackEntity(Entity entity) { //org.spigotmc.AsyncCatcher.catchOp( "entity untrack"); // Spigot boolean mainThread = Thread.currentThread() == MinecraftServer.getServer().primaryThread; diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/EntityTrackerEntry.java b/eSpigot-Server/src/main/java/net/minecraft/server/EntityTrackerEntry.java index 1073364..f9638d1 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/EntityTrackerEntry.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/EntityTrackerEntry.java @@ -461,7 +461,7 @@ public class EntityTrackerEntry { return d0 >= (double) (-this.b) && d0 <= (double) this.b && d1 >= (double) (-this.b) && d1 <= (double) this.b && this.tracker.a(entityplayer); } - private boolean e(EntityPlayer entityplayer) { + protected boolean e(EntityPlayer entityplayer) { // IonSpigot - private -> protected return entityplayer.u().getPlayerChunkMap().a(entityplayer, this.tracker.ae, this.tracker.ag); } @@ -471,7 +471,7 @@ public class EntityTrackerEntry { } } - private Packet c() { + protected Packet c() { if (this.tracker.dead) { // CraftBukkit start - Remove useless error spam, just return // EntityTrackerEntry.p.warn("Fetching addPacket for removed entity"); diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/EnumProtocol.java b/eSpigot-Server/src/main/java/net/minecraft/server/EnumProtocol.java index 393ffc8..33c782b 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/EnumProtocol.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/EnumProtocol.java @@ -174,6 +174,10 @@ public enum EnumProtocol { return this.i; } + public int getStateId() { // OBFHELPER + return a(); + } + public static EnumProtocol a(int i) { return i >= EnumProtocol.e && i <= EnumProtocol.f ? EnumProtocol.g[i - EnumProtocol.e] : null; } @@ -182,6 +186,10 @@ public enum EnumProtocol { return (EnumProtocol) EnumProtocol.h.get(packet.getClass()); } + public static EnumProtocol getProtocolForPacket(Packet packet) { // OBFHELPER + return a(packet); + } + EnumProtocol(int i, Object object) { this(i); } diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/Explosion.java b/eSpigot-Server/src/main/java/net/minecraft/server/Explosion.java index 7608b5b..c7ef491 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/Explosion.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/Explosion.java @@ -1,26 +1,27 @@ package net.minecraft.server; +import com.elevatemc.spigot.config.eSpigotConfig; +import com.elevatemc.spigot.threading.ThreadingManager; import com.elevatemc.spigot.util.Constants; -import com.elevatemc.spigot.util.FastRandom; import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import com.google.common.collect.Sets; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Random; +// Nacho start +import net.jafama.FastMath; +// Nacho end // CraftBukkit start -import org.bukkit.craftbukkit.event.CraftEventFactory; -import org.bukkit.event.entity.EntityExplodeEvent; import org.bukkit.Location; +import org.bukkit.craftbukkit.event.CraftEventFactory; import org.bukkit.event.block.BlockExplodeEvent; +import org.bukkit.event.entity.EntityExplodeEvent; // CraftBukkit end +import java.util.*; +import java.util.concurrent.CompletableFuture; + public class Explosion { - public static final Random CACHED_RANDOM = new FastRandom(); // AW-Spigot - fast random + public static final Random CACHED_RANDOM = new Random(); private final boolean a; private final boolean b; private final Random c = CACHED_RANDOM; @@ -51,118 +52,110 @@ public class Explosion { return; } // CraftBukkit end - HashSet hashset = Sets.newHashSet(); - boolean flag = true; + // HashSet hashset = Sets.newHashSet(); int i; int j; - Block b = world.getChunkAt((int)posX >> 4, (int)posZ >> 4).getBlockData(new BlockPosition(posX, posY, posZ)).getBlock(); // TacoSpigot - get block of the explosion + // IonSpigot start - Block Searching Improvements + BlockPosition pos = new BlockPosition(posX, posY, posZ); + Chunk chunk = world.getChunkAt(pos.getX() >> 4, pos.getZ() >> 4); + Block b = chunk.getBlockData(pos).getBlock(); // TacoSpigot - get block of the explosion - if (!this.world.paperSpigotConfig.optimizeLiquidExplosions || !b.getMaterial().isLiquid()) { //TacoSpigot - skip calculating what blocks to blow up in water/lava - for (int k = 0; k < 16; ++k) { - for (i = 0; i < 16; ++i) { - for (j = 0; j < 16; ++j) { - if (k == 0 || k == 15 || i == 0 || i == 15 || j == 0 || j == 15) { - double d0 = (float) k / 15.0F * 2.0F - 1.0F; - double d1 = (float) i / 15.0F * 2.0F - 1.0F; - double d2 = (float) j / 15.0F * 2.0F - 1.0F; - double d3 = Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2); - - d0 /= d3; - d1 /= d3; - d2 /= d3; - float f = this.size * (0.7F + this.world.random.nextFloat() * 0.6F); - double d4 = this.posX; - double d5 = this.posY; - double d6 = this.posZ; - - for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) { - BlockPosition blockposition = new BlockPosition(d4, d5, d6); - IBlockData iblockdata = this.world.getType(blockposition); - - if (iblockdata.getBlock().getMaterial() != Material.AIR) { - float f2 = this.source != null ? this.source.a(this, this.world, blockposition, iblockdata) : iblockdata.getBlock().a((Entity) null); - - f -= (f2 + 0.3F) * 0.3F; - } - - if (f > 0.0F && (this.source == null || this.source.a(this, this.world, blockposition, iblockdata, f)) && blockposition.getY() < 256 && blockposition.getY() >= 0) { // CraftBukkit - don't wrap explosions - hashset.add(blockposition); - } - - d4 += d0 * 0.30000001192092896D; - d5 += d1 * 0.30000001192092896D; - d6 += d2 * 0.30000001192092896D; - } - } - } + if (!this.world.paperSpigotConfig.optimizeLiquidExplosions || !b.getMaterial().isLiquid()) { // TacoSpigot - skip calculating what blocks to blow up in water/lava + it.unimi.dsi.fastutil.longs.LongSet set = new it.unimi.dsi.fastutil.longs.LongOpenHashSet(); + searchForBlocks(set, chunk); + for (it.unimi.dsi.fastutil.longs.LongIterator iterator = set.iterator(); iterator.hasNext(); ) { + this.blocks.add(BlockPosition.fromLong(iterator.nextLong())); } } - } - this.blocks.addAll(hashset); + // this.blocks.addAll(hashset); float f3 = this.size * 2.0F; - i = MathHelper.floor(this.posX - (double) f3 - 1.0D); - j = MathHelper.floor(this.posX + (double) f3 + 1.0D); - int l = MathHelper.floor(this.posY - (double) f3 - 1.0D); - int i1 = MathHelper.floor(this.posY + (double) f3 + 1.0D); - int j1 = MathHelper.floor(this.posZ - (double) f3 - 1.0D); - int k1 = MathHelper.floor(this.posZ + (double) f3 + 1.0D); + // IonSpigot start - Faster Entity Iteration + i = MathHelper.floor(this.posX - (double) f3 - 1.0D) >> 4; + j = MathHelper.floor(this.posX + (double) f3 + 1.0D) >> 4; + int l = MathHelper.clamp(MathHelper.floor(this.posY - (double) f3 - 1.0D) >> 4, 0, 15); + int i1 = MathHelper.clamp(MathHelper.floor(this.posY + (double) f3 + 1.0D) >> 4, 0, 15); + int j1 = MathHelper.floor(this.posZ - (double) f3 - 1.0D) >> 4; + int k1 = MathHelper.floor(this.posZ + (double) f3 + 1.0D) >> 4; // PaperSpigot start - Fix lag from explosions processing dead entities - List list = this.world.a(this.source, new AxisAlignedBB(i, l, j1, j, i1, k1), entity -> IEntitySelector.d.apply(entity) && !entity.dead); + // List list = this.world.a(this.source, new AxisAlignedBB(i, l, j1, j, i1, k1), entity -> IEntitySelector.d.apply(entity) && !entity.dead); // PaperSpigot end Vec3D vec3d = new Vec3D(this.posX, this.posY, this.posZ); - for (Object o : list) { - Entity entity = (Entity) o; + for (int chunkX = i; chunkX <= j; ++chunkX) { + for (int chunkZ = j1; chunkZ <= k1; ++chunkZ) { + chunk = world.getChunkIfLoaded(chunkX, chunkZ); + if (chunk == null) { + continue; + } + + for (int chunkY = l; chunkY <= i1; ++chunkY) { + affectEntities(chunk.entitySlices[chunkY], vec3d, f3); + } + } + } + } + + public void affectEntities(List list, Vec3D vec3d, float f3) { + for (Entity entity : list) { if (!entity.aW()) { - double d7 = entity.f(this.posX, this.posY, this.posZ) / (double) f3; - - if (d7 <= 1.0D) { + if (!entity.dead) { double d8 = entity.locX - this.posX; - double d9 = entity.locY + (double) entity.getHeadHeight() - this.posY; + double d9 = entity.locY + entity.getHeadHeight() - this.posY; double d10 = entity.locZ - this.posZ; - double d11 = MathHelper.sqrt(d8 * d8 + d9 * d9 + d10 * d10); + double distanceSquared = d8 * d8 + d9 * d9 + d10 * d10; - if (d11 != 0.0D) { + if (distanceSquared <= 64.0D && distanceSquared != 0.0D) { + double d11 = MathHelper.sqrt(distanceSquared); + double d7 = d11 / (double) f3; d8 /= d11; d9 /= d11; d10 /= d11; - double d12 = this.getBlockDensity(vec3d, entity.getBoundingBox()); // PaperSpigot - Optimize explosions - double d13 = (1.0D - d7) * d12; - // entity.damageEntity(DamageSource.explosion(this), (float) ((int) ((d13 * d13 + d13) / 2.0D * 8.0D * (double) f3 + 1.0D)));+ // CraftBukkit start - CraftEventFactory.entityDamage = source; - entity.forceExplosionKnockback = false; - boolean wasDamaged = entity.damageEntity(DamageSource.explosion(this), (float) ((int) ((d13 * d13 + d13) / 2.0D * 8.0D * (double) f3 + 1.0D))); - CraftEventFactory.entityDamage = null; - if (!wasDamaged && !(entity instanceof EntityTNTPrimed || entity instanceof EntityFallingBlock) && !entity.forceExplosionKnockback) { - continue; - } - // CraftBukkit end - double d14 = entity instanceof EntityHuman && world.paperSpigotConfig.disableExplosionKnockback ? 0 : EnchantmentProtection.a(entity, d13); // PaperSpigot + // Paper - Optimize explosions + // double d12 = this.getBlockDensity(vec3d, entity); + double finalD = d8; + double finalD1 = d9; + double finalD11 = d10; + this.getBlockDensity(vec3d, entity.getBoundingBox()).thenAccept((d12) -> MinecraftServer.getServer().addMainThreadTask(() -> { + double d13 = (1.0D - d7) * d12; - // PaperSpigot start - Fix cannons - /* - entity.motX += d8 * d14; - entity.motY += d9 * d14; - entity.motZ += d10 * d14; - */ - // This impulse method sets the dirty flag, so clients will get an immediate velocity update - entity.g(d8 * d14, d9 * d14, d10 * d14); - // PaperSpigot end + if (entity.isCannoningEntity) { + entity.g(finalD * d13, finalD1 * d13, finalD11 * d13); + return; + } + // IonSpigot end - if (entity instanceof EntityHuman && !((EntityHuman) entity).abilities.isInvulnerable && !world.paperSpigotConfig.disableExplosionKnockback) { // PaperSpigot - this.k.put((EntityHuman) entity, new Vec3D(d8 * d13, d9 * d13, d10 * d13)); - } + // entity.damageEntity(DamageSource.explosion(this), (float) ((int) ((d13 * d13 + d13) / 2.0D * 8.0D * (double) f3 + 1.0D))); // CraftBukkit start + CraftEventFactory.entityDamage = source; + entity.forceExplosionKnockback = false; + boolean wasDamaged = entity.damageEntity(DamageSource.explosion(this), (float) ((int) ((d13 * d13 + d13) / 2.0D * 8.0D * (double) f3 + 1.0D))); + CraftEventFactory.entityDamage = null; + + if (!wasDamaged && !(entity instanceof EntityTNTPrimed || entity instanceof EntityFallingBlock) && !entity.forceExplosionKnockback) { + return; + } + + // CraftBukkit end + double d14 = entity instanceof EntityHuman && world.paperSpigotConfig.disableExplosionKnockback ? 0 : EnchantmentProtection.a(entity, d13); // PaperSpigot + + // PaperSpigot start - Fix cannons + // This impulse method sets the dirty flag, so clients will get an immediate velocity update + entity.g(finalD * d14, finalD1 * d14, finalD11 * d14); + // PaperSpigot end + + if (entity instanceof EntityHuman && !((EntityHuman) entity).abilities.isInvulnerable && !world.paperSpigotConfig.disableExplosionKnockback) { // PaperSpigot + this.k.put((EntityHuman) entity, new Vec3D(finalD * d13, finalD1 * d13, finalD11 * d13)); + } + })); } } } } - } public void a(boolean flag) { @@ -194,18 +187,20 @@ public class Explosion { } } - boolean cancelled; - List bukkitBlocks; - float yield; + boolean cancelled = false; + List bukkitBlocks = blockList; + float yield = 0.3F; // default if (explode != null) { - EntityExplodeEvent event = new EntityExplodeEvent(explode, location, blockList, 0.3F); - this.world.getServer().getPluginManager().callEvent(event); - cancelled = event.isCancelled(); - bukkitBlocks = event.blockList(); - yield = event.getYield(); + if (eSpigotConfig.fireEntityExplodeEvent) { + EntityExplodeEvent event = new EntityExplodeEvent(explode, location, blockList, yield); + this.world.getServer().getPluginManager().callEvent(event); + cancelled = event.isCancelled(); + bukkitBlocks = event.blockList(); + yield = event.getYield(); + } } else { - BlockExplodeEvent event = new BlockExplodeEvent(location.getBlock(), blockList, 0.3F); + BlockExplodeEvent event = new BlockExplodeEvent(location.getBlock(), blockList, yield); this.world.getServer().getPluginManager().callEvent(event); cancelled = event.isCancelled(); bukkitBlocks = event.blockList(); @@ -231,6 +226,8 @@ public class Explosion { Block block = this.world.getType(blockposition).getBlock(); world.spigotConfig.antiXrayInstance.updateNearbyBlocks(world, blockposition); // Spigot + // IonSpigot start - Optimise Explosions + /* if (flag) { double d0 = (float) blockposition.getX() + this.world.random.nextFloat(); double d1 = (float) blockposition.getY() + this.world.random.nextFloat(); @@ -249,9 +246,11 @@ public class Explosion { d3 *= d7; d4 *= d7; d5 *= d7; - this.world.addParticle(EnumParticle.EXPLOSION_NORMAL, (d0 + this.posX * 1.0D) / 2.0D, (d1 + this.posY * 1.0D) / 2.0D, (d2 + this.posZ * 1.0D) / 2.0D, d3, d4, d5, Constants.EMPTY_ARRAY); - this.world.addParticle(EnumParticle.SMOKE_NORMAL, d0, d1, d2, d3, d4, d5, Constants.EMPTY_ARRAY); + this.world.addParticle(EnumParticle.EXPLOSION_NORMAL, (d0 + this.posX) / 2.0D, (d1 + this.posY) / 2.0D, (d2 + this.posZ) / 2.0D, d3, d4, d5); + this.world.addParticle(EnumParticle.SMOKE_NORMAL, d0, d1, d2, d3, d4, d5); } + */ + // IonSpigot end if (block.getMaterial() != Material.AIR) { if (block.a(this)) { @@ -270,6 +269,7 @@ public class Explosion { while (iterator.hasNext()) { blockposition = (BlockPosition) iterator.next(); + // Nacho - revert >> // Nacho - optimize TNT by Lew_x if (this.world.getType(blockposition).getBlock().getMaterial() == Material.AIR && this.world.getType(blockposition.down()).getBlock().o() && this.c.nextInt(3) == 0) { // CraftBukkit start - Ignition by explosion if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(this.world, blockposition.getX(), blockposition.getY(), blockposition.getZ(), this).isCancelled()) { @@ -300,22 +300,184 @@ public class Explosion { return this.blocks; } - // PaperSpigot start - Optimize explosions - private float getBlockDensity(Vec3D vec3d, AxisAlignedBB aabb) { - if (!this.world.paperSpigotConfig.optimizeExplosions) { - return this.world.a(vec3d, aabb); - } + // IonSpigot start - Block Searching Improvements + private final static List VECTORS = Lists.newArrayListWithCapacity(1352); - CacheKey key = new CacheKey(this, aabb); - Float blockDensity = this.world.explosionDensityCache.get(key); - if (blockDensity == null) { - blockDensity = this.world.a(vec3d, aabb); - this.world.explosionDensityCache.put(key, blockDensity); - } + static { + for (int k = 0; k < 16; ++k) { + for (int i = 0; i < 16; ++i) { + for (int j = 0; j < 16; ++j) { + if (k == 0 || k == 15 || i == 0 || i == 15 || j == 0 || j == 15) { + double d0 = (float) k / 15.0F * 2.0F - 1.0F; + double d1 = (float) i / 15.0F * 2.0F - 1.0F; + double d2 = (float) j / 15.0F * 2.0F - 1.0F; + double d3 = (eSpigotConfig.fastMath ? FastMath.sqrt(d0 * d0 + d1 * d1 + d2 * d2) : Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2)); - return blockDensity; + d0 = (d0 / d3) * 0.30000001192092896D; + d1 = (d1 / d3) * 0.30000001192092896D; + d2 = (d2 / d3) * 0.30000001192092896D; + VECTORS.add(new double[]{d0, d1, d2}); + } + } + } + } } + // https://github.com/jellysquid3/lithium-fabric/blob/1.16.x/dev/src/main/java/me/jellysquid/mods/lithium/mixin/world/explosions/ExplosionMixin.java + private void searchForBlocks(it.unimi.dsi.fastutil.longs.LongSet set, Chunk chunk) { + BlockPosition.MutableBlockPosition position = new BlockPosition.MutableBlockPosition(); + + for (double[] vector : VECTORS) { + double d0 = vector[0]; + double d1 = vector[1]; + double d2 = vector[2]; + + float f = this.size * (0.7F + (world.paperSpigotConfig.constantExplosions ? 0.7F : this.world.random.nextFloat()) * 0.6F); + float resistance = 0; + + double stepX = this.posX; + double stepY = this.posY; + double stepZ = this.posZ; + + for (; f > 0.0F; f -= 0.22500001F) { + int floorX = (eSpigotConfig.fastMath ? FastMath.floorToInt((Double.doubleToRawLongBits(stepX) >>> 63)) : org.bukkit.util.NumberConversions.floor(stepX)); + int floorY = (eSpigotConfig.fastMath ? FastMath.floorToInt((Double.doubleToRawLongBits(stepY) >>> 63)) : org.bukkit.util.NumberConversions.floor(stepY)); + int floorZ = (eSpigotConfig.fastMath ? FastMath.floorToInt((Double.doubleToRawLongBits(stepZ) >>> 63)) : org.bukkit.util.NumberConversions.floor(stepZ)); + + if (position.getX() != floorX || position.getY() != floorY || position.getZ() != floorZ) { + position.setValues(floorX, floorY, floorZ); + + int chunkX = floorX >> 4; + int chunkZ = floorZ >> 4; + if (chunk == null || !chunk.o() || chunk.locX != chunkX || chunk.locZ != chunkZ) { + chunk = world.getChunkAt(chunkX, chunkZ); + } + + IBlockData iblockdata = chunk.getBlockData(position); + Block block = iblockdata.getBlock(); + + if (block != Blocks.AIR) { + float blockResistance = block.durability / 5.0f; + resistance = (blockResistance + 0.3F) * 0.3F; + f -= resistance; + + if (f > 0.0F && (this.source == null || this.source.a(this, this.world, position, iblockdata, f)) && position.getY() < 256 && position.getY() >= 0) { // CraftBukkit - don't wrap explosions + set.add(position.asLong()); + } + } + } else { + f -= resistance; + } + + stepX += d0; + stepY += d1; + stepZ += d2; + } + } + } + // IonSpigot end + + // Paper start - Optimize explosions + private CompletableFuture getBlockDensity(Vec3D vec3d, AxisAlignedBB aabb) { + return CompletableFuture.supplyAsync(() -> { + // IonSpigot start - Optimise Density Cache + int key = createKey(this, aabb); + float blockDensity = this.world.explosionDensityCache.get(key); + if (blockDensity == -1.0f) { + blockDensity = calculateDensity(vec3d, aabb); + this.world.explosionDensityCache.put(key, blockDensity); + } + return blockDensity; + }, ThreadingManager.asyncExplosionsExecutor); + } + + private float calculateDensity(Vec3D vec3d, AxisAlignedBB aabb) { + if (world.paperSpigotConfig.reducedDensityRays) { + return calculateDensityReducedRays(vec3d, aabb); + } else { + return this.world.a(vec3d, aabb); + } + } + + private float calculateDensityReducedRays(Vec3D vec3d, AxisAlignedBB aabb) { + int arrived = 0; + int rays = 0; + + for (Vec3D vector : calculateVectors(aabb)) { + // If rays from the corners don't hit a block + // it should be safe to return the best outcome + if (rays == 8 && arrived == 8) { + return 1.0F; + } + + if (world.rayTrace(vector, vec3d) == null) { + ++arrived; + } + + ++rays; + } + + return (float) arrived / (float) rays; + } + + private List calculateVectors(AxisAlignedBB aabb) { + double d0 = 1.0D / ((aabb.d - aabb.a) * 2.0D + 1.0D); + double d1 = 1.0D / ((aabb.e - aabb.b) * 2.0D + 1.0D); + double d2 = 1.0D / ((aabb.f - aabb.c) * 2.0D + 1.0D); + double d3 = (1.0D - ((eSpigotConfig.fastMath ? FastMath.floor(1.0D / d0) : Math.floor(1.0D / d0)) * d0)) / 2.0D; + double d4 = (1.0D - ((eSpigotConfig.fastMath ? FastMath.floor(1.0D / d2) : Math.floor(1.0D / d2)) * d2)) / 2.0D; + + if (d0 < 0.0 || d1 < 0.0 || d2 < 0.0) { + return Collections.emptyList(); + } + + List vectors = new LinkedList<>(); + + for (float f = 0.0F; f <= 1.0F; f = (float) ((double) f + d0)) { + for (float f1 = 0.0F; f1 <= 1.0F; f1 = (float) ((double) f1 + d1)) { + for (float f2 = 0.0F; f2 <= 1.0F; f2 = (float) ((double) f2 + d2)) { + double d5 = aabb.a + (aabb.d - aabb.a) * (double) f; + double d6 = aabb.b + (aabb.e - aabb.b) * (double) f1; + double d7 = aabb.c + (aabb.f - aabb.c) * (double) f2; + Vec3D vector = new Vec3D(d5 + d3, d6, d7 + d4); + + if ((f == 0 || f + d0 > 1.0F) && (f1 == 0 || f1 + d1 > 1.0F) && (f2 == 0 || f2 + d2 > 1.0F)) { + vectors.add(0, vector); + } else { + vectors.add(vector); + } + } + } + } + + return vectors; + } + + static int createKey(Explosion explosion, AxisAlignedBB aabb) { + int result; + long temp; + result = explosion.world.hashCode(); + temp = Double.doubleToLongBits(explosion.posX); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(explosion.posY); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(explosion.posZ); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(aabb.a); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(aabb.b); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(aabb.c); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(aabb.d); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(aabb.e); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(aabb.f); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + return result; + } + /* :: IonSpigot - comment this out static class CacheKey { private final World world; private final double posX, posY, posZ; @@ -327,12 +489,12 @@ public class Explosion { this.posX = explosion.posX; this.posY = explosion.posY; this.posZ = explosion.posZ; - this.minX = aabb.a; - this.minY = aabb.b; - this.minZ = aabb.c; - this.maxX = aabb.d; - this.maxY = aabb.e; - this.maxZ = aabb.f; + this.minX = aabb.getMinX(); + this.minY = aabb.getMinY(); + this.minZ = aabb.getMinZ(); + this.maxX = aabb.getMaxX(); + this.maxY = aabb.getMaxY(); + this.maxZ = aabb.getMaxZ(); } @Override @@ -380,5 +542,7 @@ public class Explosion { return result; } } - // PaperSpigot end -} + */ + // IonSpigot end + // Paper end +} \ No newline at end of file diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/LoginListener.java b/eSpigot-Server/src/main/java/net/minecraft/server/LoginListener.java index d02d577..b266866 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/LoginListener.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/LoginListener.java @@ -139,7 +139,7 @@ public class LoginListener implements PacketLoginInListener, IUpdatePlayerListBo if (this.server.aK() >= 0 && !this.networkManager.c()) { this.networkManager.a(new PacketLoginOutSetCompression(this.server.aK()), new ChannelFutureListener() { public void a(ChannelFuture channelfuture) throws Exception { - LoginListener.this.networkManager.a(LoginListener.this.server.aK()); + LoginListener.this.networkManager.setupCompression(LoginListener.this.server.aK()); } public void operationComplete(ChannelFuture future) throws Exception { // CraftBukkit - fix decompile error diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/MathHelper.java b/eSpigot-Server/src/main/java/net/minecraft/server/MathHelper.java index 49e0646..f4d9950 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/MathHelper.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/MathHelper.java @@ -1,5 +1,8 @@ package net.minecraft.server; +import com.elevatemc.spigot.config.eSpigotConfig; +import net.jafama.FastMath; + import java.util.Random; import java.util.UUID; @@ -11,30 +14,34 @@ public class MathHelper { private static final double d; private static final double[] e; private static final double[] f; + private static final boolean fastMathMode = eSpigotConfig.fastMath; + private static final boolean fastMathCosSin = eSpigotConfig.fastMathCosSin; public static float sin(float f) { - return MathHelper.b[(int) (f * 10430.378F) & '\uffff']; + return (fastMathCosSin ? ((float) FastMath.sinQuick(b[(int)(f * 10430.378F) & '\uffff'])) : (b[(int)(f * 10430.378F) & '\uffff'])); } public static float cos(float f) { - return MathHelper.b[(int) (f * 10430.378F + 16384.0F) & '\uffff']; + return (fastMathCosSin ? ((float) FastMath.cosQuick(b[(int)(f * 10430.378F + 16384.0F) & '\uffff'])) : (b[(int)(f * 10430.378F + 16384.0F) & '\uffff'])); } public static float c(float f) { - return (float) Math.sqrt((double) f); + return (float) (fastMathMode ? (FastMath.sqrt(f)) : (Math.sqrt(f))); } public static float sqrt(double d0) { - return (float) Math.sqrt(d0); + return (float) (fastMathMode ? (FastMath.sqrt(d0)) : (Math.sqrt(d0))); } public static int d(float f) { + if (fastMathMode) return FastMath.floorToInt(f); int i = (int) f; return f < (float) i ? i - 1 : i; } public static int floor(double d0) { + if (fastMathMode) return FastMath.floorToInt(d0); int i = (int) d0; return d0 < (double) i ? i - 1 : i; @@ -55,12 +62,15 @@ public class MathHelper { } public static int f(float f) { + if (fastMathMode) return FastMath.ceilToInt(f); int i = (int) f; return f > (float) i ? i + 1 : i; } public static int f(double d0) { + if (fastMathMode) return FastMath.ceilToInt(d0); + int i = (int) d0; return d0 > (double) i ? i + 1 : i; @@ -155,7 +165,7 @@ public class MathHelper { } public static int a(String s, int i, int j) { - return Math.max(j, a(s, i)); + return (fastMathMode ? (FastMath.max(j, a(s, i))) : (Math.max(j, a(s, i)))); } public static double a(String s, double d0) { @@ -261,11 +271,11 @@ public class MathHelper { double d10 = d5 + d9; if (flag2) { - d10 = 1.5707963267948966D - d10; + d10 = (fastMathMode ? (FastMath.PI / 2) : (1.5707963267948966D)) - d10; } if (flag1) { - d10 = 3.141592653589793D - d10; + d10 = (fastMathMode ? (FastMath.PI) : (3.141592653589793D)) - d10; } if (flag) { @@ -300,7 +310,7 @@ public class MathHelper { for (i = 0; i < 257; ++i) { double d0 = (double) i / 256.0D; - double d1 = Math.asin(d0); + double d1 = (fastMathMode ? (FastMath.asin(d0)) : (Math.asin(d0)));; MathHelper.f[i] = Math.cos(d1); MathHelper.e[i] = d1; diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/MinecraftServer.java b/eSpigot-Server/src/main/java/net/minecraft/server/MinecraftServer.java index 088ab6c..cd2767d 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/MinecraftServer.java @@ -598,7 +598,7 @@ public abstract class MinecraftServer implements Runnable, ICommandListener, IAs } } - public void stop() throws ExceptionWorldConflict { // CraftBukkit - added throws + public void stop() throws ExceptionWorldConflict, InterruptedException { // CraftBukkit - added throws // CraftBukkit start - prevent double stopping on multiple threads synchronized (stopLock) { if (hasStopped) return; @@ -615,7 +615,7 @@ public abstract class MinecraftServer implements Runnable, ICommandListener, IAs } // CraftBukkit end if (this.aq() != null) { - this.aq().b(); + this.aq().stopServer(); } if (this.v != null) { @@ -997,7 +997,7 @@ public abstract class MinecraftServer implements Runnable, ICommandListener, IAs if (this.getPlayerList().getPlayerCount() != 0) // Tuinity { // Tuinity start - controlled flush for entity tracker packets - List disabledFlushes = new java.util.ArrayList<>(this.getPlayerList().getPlayerCount()); + Set disabledFlushes = new HashSet<>(this.getPlayerList().getPlayerCount()); for (EntityPlayer player : this.getPlayerList().players) { PlayerConnection connection = player.playerConnection; if (connection != null) { @@ -1406,6 +1406,8 @@ public abstract class MinecraftServer implements Runnable, ICommandListener, IAs public abstract boolean ai(); + public abstract ServerConnection.EventGroupType getTransport(); + public boolean getPVP() { return this.pvpMode; } diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/NetworkManager.java b/eSpigot-Server/src/main/java/net/minecraft/server/NetworkManager.java index 478fbc6..19125b7 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/NetworkManager.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/NetworkManager.java @@ -1,69 +1,61 @@ package net.minecraft.server; import com.elevatemc.spigot.eSpigot; -import com.elevatemc.spigot.util.CryptException; +import com.elevatemc.spigot.exception.CryptException; +import com.elevatemc.spigot.exception.ExploitException; +import com.velocitypowered.natives.compression.VelocityCompressor; // Paper +import com.velocitypowered.natives.util.Natives; // Paper import com.google.common.collect.Queues; -import com.google.common.util.concurrent.ThreadFactoryBuilder; -import com.velocitypowered.natives.compression.VelocityCompressor; -import com.velocitypowered.natives.util.Natives; -import io.netty.buffer.PooledByteBufAllocator; import io.netty.channel.*; -import io.netty.channel.epoll.EpollEventLoopGroup; import io.netty.channel.local.LocalChannel; -import io.netty.channel.local.LocalEventLoopGroup; import io.netty.channel.local.LocalServerChannel; -import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.handler.codec.DecoderException; import io.netty.handler.timeout.TimeoutException; import io.netty.util.AttributeKey; +import io.netty.util.concurrent.AbstractEventExecutor; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; +import java.net.SocketAddress; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.locks.ReentrantReadWriteLock; + import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.Validate; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.MarkerManager; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.entity.CraftPlayer; -import java.net.SocketAddress; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.locks.ReentrantReadWriteLock; +public class NetworkManager extends SimpleChannelInboundHandler> { -public class NetworkManager extends SimpleChannelInboundHandler { + private static final Logger LOGGER = LogManager.getLogger(); + public static final Marker ROOT_MARKER = MarkerManager.getMarker("NETWORK"); + public static final Marker PACKET_MARKER = MarkerManager.getMarker("NETWORK_PACKETS", NetworkManager.ROOT_MARKER); + public static final AttributeKey ATTRIBUTE_PROTOCOL = AttributeKey.valueOf("protocol"); + public static final AttributeKey c = ATTRIBUTE_PROTOCOL; - public static final Marker a = MarkerManager.getMarker("NETWORK"); - public static final Marker b = MarkerManager.getMarker("NETWORK_PACKETS", NetworkManager.a); - public static final AttributeKey c = AttributeKey.valueOf("protocol"); - public static final LazyInitVar d = new LazyInitVar() { - protected NioEventLoopGroup a() { - return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Client IO #%d").setDaemon(true).build()); - } + private final EnumProtocolDirection h; + private final Queue i = Queues.newConcurrentLinkedQueue(); + private final ReentrantReadWriteLock j = new ReentrantReadWriteLock(); + public Channel channel; + // Spigot Start // PAIL + public SocketAddress l; + public java.util.UUID spoofedUUID; + public com.mojang.authlib.properties.Property[] spoofedProfile; + public boolean preparing = true; + // Spigot End + private PacketListener m; + private IChatBaseComponent n; + private boolean encrypted; // Nacho - deobfuscate + private boolean isDisconnectionHandled; // Nacho - deobfuscate - protected Object init() { - return this.a(); - } - }; - public static final LazyInitVar e = new LazyInitVar() { - protected EpollEventLoopGroup a() { - return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Client IO #%d").setDaemon(true).build()); - } - - protected Object init() { - return this.a(); - } - }; - public static final LazyInitVar f = new LazyInitVar() { - protected LocalEventLoopGroup a() { - return new LocalEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Local Client IO #%d").setDaemon(true).build()); - } - - protected Object init() { - return this.a(); - } - }; - private static final Logger g = LogManager.getLogger(); + // eSpigot start - faster packets + private final Queue fastPackets = Queues.newConcurrentLinkedQueue(); public static final Set>> FAST_ELIGIBLE = new LinkedHashSet<>(); static { @@ -75,31 +67,13 @@ public class NetworkManager extends SimpleChannelInboundHandler { FAST_ELIGIBLE.add(PacketPlayOutEntity.PacketPlayOutRelEntityMoveLook.class); FAST_ELIGIBLE.add(PacketPlayOutAnimation.class); } + // eSpigot end - private final EnumProtocolDirection h; - private final ConcurrentLinkedQueue i = Queues.newConcurrentLinkedQueue(); - private final ConcurrentLinkedQueue fastPackets = Queues.newConcurrentLinkedQueue(); // eSpigot - Process combat packets faster - private final ReentrantReadWriteLock j = new ReentrantReadWriteLock(); - private final java.util.concurrent.atomic.AtomicInteger packetWrites = new java.util.concurrent.atomic.AtomicInteger(); - private final Object flushLock = new Object(); - public Channel channel; - // Spigot Start // PAIL - public SocketAddress l; - public java.util.UUID spoofedUUID; - public com.mojang.authlib.properties.Property[] spoofedProfile; - public boolean preparing = true; // Tuinity start - allow controlled flushing volatile boolean canFlush = true; - // Spigot End - private PacketListener m; - private IChatBaseComponent n; - private boolean o; - private boolean p; + private final java.util.concurrent.atomic.AtomicInteger packetWrites = new java.util.concurrent.atomic.AtomicInteger(); private int flushPacketsStart; - - public NetworkManager(EnumProtocolDirection enumprotocoldirection) { - this.h = enumprotocoldirection; - } + private final Object flushLock = new Object(); void disableAutomaticFlush() { synchronized (this.flushLock) { @@ -109,56 +83,50 @@ public class NetworkManager extends SimpleChannelInboundHandler { } void enableAutomaticFlush() { - synchronized (this.flushLock) { + synchronized (this.flushLock) + { this.canFlush = true; - if (this.packetWrites.get() != this.flushPacketsStart) // must be after canFlush = true + if (this.packetWrites.get() != this.flushPacketsStart) { // must be after canFlush = true this.flush(); // only make the flush call if we need to + } } } + + private void flush() + { + if (this.channel.eventLoop().inEventLoop()) { + this.channel.flush(); + } //[Nacho-Spigot] Fixed RejectedExecutionException: event executor terminated by BeyazPolis + } // Tuinity end - allow controlled flushing - private void flush() { - if (this.channel.eventLoop().inEventLoop()) - this.channel.flush(); + public NetworkManager(EnumProtocolDirection enumprotocoldirection) { + this.h = enumprotocoldirection; } public void channelActive(ChannelHandlerContext channelhandlercontext) throws Exception { super.channelActive(channelhandlercontext); this.channel = channelhandlercontext.channel(); this.l = this.channel.remoteAddress(); - // Spigot Start this.preparing = false; // Spigot End try { - this.a(EnumProtocol.HANDSHAKING); + this.setProtocol(EnumProtocol.HANDSHAKING); } catch (Throwable throwable) { - NetworkManager.g.fatal(throwable); + NetworkManager.LOGGER.fatal(throwable); } } - public void setupEncryption(javax.crypto.SecretKey key) throws CryptException { - if (!this.o) { - try { - com.velocitypowered.natives.encryption.VelocityCipher decryption = com.velocitypowered.natives.util.Natives.cipher.get().forDecryption(key); - com.velocitypowered.natives.encryption.VelocityCipher encryption = com.velocitypowered.natives.util.Natives.cipher.get().forEncryption(key); - - this.o = true; - this.channel.pipeline().addBefore("splitter", "decrypt", new PacketDecrypter(decryption)); - this.channel.pipeline().addBefore("prepender", "encrypt", new PacketEncrypter(encryption)); - } catch (java.security.GeneralSecurityException e) { - throw new CryptException(e); - } - } + public void setProtocol(EnumProtocol protocol) { + a(protocol); } - // Paper end - public void a(EnumProtocol enumprotocol) { - this.channel.attr(NetworkManager.c).set(enumprotocol); + public void a(EnumProtocol protocol) { + this.channel.attr(NetworkManager.ATTRIBUTE_PROTOCOL).set(protocol); this.channel.config().setAutoRead(true); - NetworkManager.g.debug("Enabled auto read"); } public void channelInactive(ChannelHandlerContext channelhandlercontext) throws Exception { @@ -168,6 +136,20 @@ public class NetworkManager extends SimpleChannelInboundHandler { public void exceptionCaught(ChannelHandlerContext channelhandlercontext, Throwable throwable) throws Exception { ChatMessage chatmessage; + if(throwable instanceof DecoderException) { + DecoderException decoderException = ((DecoderException) throwable); + if(decoderException.getCause() instanceof ExploitException) { + Bukkit.getLogger().warning("Server crash detected..."); + if(this.getPacketListener() != null && this.getPacketListener() instanceof PlayerConnection) { + PlayerConnection playerConnection = (PlayerConnection) this.getPacketListener(); + CraftPlayer player = playerConnection.getPlayer(); + if(player != null) { + Bukkit.getLogger().warning(player.getName() + " has tried to crash the server... " + decoderException.getCause()); + } + } + } + } + if (throwable instanceof TimeoutException) { chatmessage = new ChatMessage("disconnect.timeout"); } else { @@ -175,7 +157,7 @@ public class NetworkManager extends SimpleChannelInboundHandler { } this.close(chatmessage); - if (MinecraftServer.getServer().isDebugging()) throwable.printStackTrace(); // Spigot +// if (MinecraftServer.getServer().isDebugging()) throwable.printStackTrace(); // Spigot } protected void a(ChannelHandlerContext channelhandlercontext, Packet packet) throws Exception { @@ -183,7 +165,6 @@ public class NetworkManager extends SimpleChannelInboundHandler { try { packet.a(this.m); - if (this.m instanceof PlayerConnection) { try { eSpigot.getInstance().getPacketHandlers().forEach(packetHandler -> packetHandler.handleReceivedPacket((PlayerConnection) this.m, packet)); @@ -199,38 +180,20 @@ public class NetworkManager extends SimpleChannelInboundHandler { public void a(PacketListener packetlistener) { Validate.notNull(packetlistener, "packetListener"); - NetworkManager.g.debug("Set listener of {} to {}", this, packetlistener); +// NetworkManager.g.debug("Set listener of {} to {}", new Object[] { this, packetlistener}); this.m = packetlistener; } + //sendPacket public void handle(Packet packet) { - if (this.g()) { - this.m(); - this.a(packet, null); - } else { // Channel closed - this.j.writeLock().lock(); - - try { - this.i.add(new NetworkManager.QueuedPacket(packet, (GenericFutureListener[]) null)); - } finally { - this.j.writeLock().unlock(); - } - } - - } - - public void a(Packet packet, GenericFutureListener> genericfuturelistener, GenericFutureListener>... agenericfuturelistener) { - if (this.g()) { - this.m(); - this.a(packet, ArrayUtils.add(agenericfuturelistener, 0, genericfuturelistener), Boolean.TRUE); + if (this.isConnected()) { + this.sendPacketQueue(); + this.dispatchPacket(packet, null, Boolean.TRUE); } else { this.j.writeLock().lock(); try { - if (FAST_ELIGIBLE.contains(packet.getClass())) - this.fastPackets.add(new NetworkManager.QueuedPacket(packet, ArrayUtils.add(agenericfuturelistener, 0, genericfuturelistener))); - else - this.i.add(new NetworkManager.QueuedPacket(packet, ArrayUtils.add(agenericfuturelistener, 0, genericfuturelistener))); + this.i.add(new NetworkManager.QueuedPacket(packet)); } finally { this.j.writeLock().unlock(); } @@ -238,104 +201,110 @@ public class NetworkManager extends SimpleChannelInboundHandler { } - private void a(final Packet packet, final GenericFutureListener>[] agenericfuturelistener, Boolean shouldFlush) { - this.packetWrites.getAndIncrement(); // must be before using canFlush - boolean effectiveFlush = shouldFlush == null ? this.canFlush : shouldFlush; - final boolean flush = effectiveFlush - || packet instanceof PacketPlayOutKeepAlive - || packet instanceof PacketPlayOutKickDisconnect - || FAST_ELIGIBLE.contains(packet.getClass()); - final EnumProtocol enumprotocol = EnumProtocol.a(packet); - final EnumProtocol enumprotocol1 = this.channel.attr(NetworkManager.c).get(); + //sendPacket + public void a(Packet packet, GenericFutureListener> listener, GenericFutureListener>... listeners) { + if (this.isConnected()) { + this.sendPacketQueue(); + this.dispatchPacket(packet, ArrayUtils.insert(0, listeners, listener), Boolean.TRUE); + } else { + this.j.writeLock().lock(); + try { + if (FAST_ELIGIBLE.contains(packet.getClass())) { + this.fastPackets.add(new NetworkManager.QueuedPacket(packet, ArrayUtils.insert(0, listeners, listener))); + } else { + this.i.add(new NetworkManager.QueuedPacket(packet, ArrayUtils.insert(0, listeners, listener))); + } + } finally { + this.j.writeLock().unlock(); + } + } + + } + + // Paper / Nacho start + public EntityPlayer getPlayer() { + if (getPacketListener() instanceof PlayerConnection) { + return ((PlayerConnection) getPacketListener()).player; + } else { + return null; + } + } + // Paper / Nacho end + + public void dispatchPacket(final Packet packet, final GenericFutureListener>[] listeners, Boolean flushConditional) { + this.packetWrites.getAndIncrement(); // must be before using canFlush + boolean effectiveFlush = flushConditional == null ? this.canFlush : flushConditional; + final boolean flush = effectiveFlush || packet instanceof PacketPlayOutKeepAlive || packet instanceof PacketPlayOutKickDisconnect || FAST_ELIGIBLE.contains(packet.getClass()); // no delay for certain packets + final EnumProtocol enumprotocol = EnumProtocol.getProtocolForPacket(packet); + final EnumProtocol enumprotocol1 = this.channel.attr(NetworkManager.ATTRIBUTE_PROTOCOL).get(); if (enumprotocol1 != enumprotocol) { - NetworkManager.g.debug("Disabled auto read"); this.channel.config().setAutoRead(false); } - if (this.channel.eventLoop().inEventLoop()) { if (enumprotocol != enumprotocol1) { - this.a(enumprotocol); + this.setProtocol(enumprotocol); } - ChannelFuture channelfuture = flush ? this.channel.writeAndFlush(packet) : this.channel.write(packet); - - if (agenericfuturelistener != null) - channelfuture.addListeners(agenericfuturelistener); - + if (listeners != null) { + channelfuture.addListeners(listeners); + } channelfuture.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); - } else { - Runnable command = new Runnable() { // PandaSpigot - optimize packets that are not flushed - public void run() { + } + else { + // Tuinity start - optimise packets that are not flushed + Runnable choice1 = null; + AbstractEventExecutor.LazyRunnable choice2 = null; + // note: since the type is not dynamic here, we need to actually copy the old executor code + // into two branches. On conflict, just re-copy - no changes were made inside the executor code. + if (flush) { + choice1 = () -> { if (enumprotocol != enumprotocol1) { - NetworkManager.this.a(enumprotocol); + this.setProtocol(enumprotocol); } try { - ChannelFuture channelfuture1 = (flush) ? NetworkManager.this.channel.writeAndFlush(packet) : NetworkManager.this.channel.write(packet); // PandaSpigot - add flush parameter - if (agenericfuturelistener != null) - channelfuture1.addListeners(agenericfuturelistener); - + ChannelFuture channelfuture1 = (flush) ? this.channel.writeAndFlush(packet) : this.channel.write(packet); // Tuinity - add flush parameter + if (listeners != null) { + channelfuture1.addListeners(listeners); + } channelfuture1.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); } catch (Exception e) { - g.error("NetworkException: ", e); - close(new ChatMessage("disconnect.genericReason", "Internal Exception: " + e.getMessage())); + LOGGER.error("NetworkException: " + getPlayer(), e); + close(new ChatMessage("disconnect.genericReason", "Internal Exception: " + e.getMessage()));; } - } - // PandaSpigot start - optimize packets that are not flushed - }; - if (!flush) { - // create a LazyRunnable that when executed, calls command.run() - io.netty.util.concurrent.AbstractEventExecutor.LazyRunnable run = command::run; - this.channel.eventLoop().execute(run); + }; } else { - // if flushing, just schedule like normal - this.channel.eventLoop().execute(command); - } - // PandaSpigot end - } - } - - private final Object fastPacketsLock = new Object(); - - private void m() { - if (this.i.isEmpty()) // Don't lock - return; - - if (this.channel != null && this.channel.isOpen()) { - this.j.readLock().lock(); - boolean needsFlush = this.canFlush; - boolean hasWrotePacket = false; - try { - Iterator iterator = this.i.iterator(); - while (iterator.hasNext()) { - QueuedPacket queued = iterator.next(); - Packet packet = queued.a; - if (hasWrotePacket && (needsFlush || this.canFlush)) flush(); - iterator.remove(); - this.a(packet, queued.b, (!iterator.hasNext() && (needsFlush || this.canFlush)) ? Boolean.TRUE : Boolean.FALSE); - hasWrotePacket = true; - } - - if (!fastPackets.isEmpty()) { - synchronized (fastPacketsLock) { - iterator = this.fastPackets.iterator(); - - while (iterator.hasNext()) { - QueuedPacket queued = iterator.next(); - Packet packet = queued.a; - if (hasWrotePacket && (needsFlush || this.canFlush)) flush(); - iterator.remove(); - this.a(packet, queued.b, (!iterator.hasNext() && (needsFlush || this.canFlush)) ? Boolean.TRUE : Boolean.FALSE); - hasWrotePacket = true; - } + // explicitly declare a variable to make the lambda use the type + choice2 = () -> { + if (enumprotocol != enumprotocol1) { + this.setProtocol(enumprotocol); } - } - } finally { - this.j.readLock().unlock(); + try { + // Nacho - why not remove the check below if the check is done above? just code duplication... + // even IntelliJ screamed at me for doing leaving it like that :shrug: + ChannelFuture channelfuture1 = /* (flush) ? this.channel.writeAndFlush(packet) : */this.channel.write(packet); // Nacho - see above // Tuinity - add flush parameter + if (listeners != null) { + channelfuture1.addListeners(listeners); + } + channelfuture1.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + } catch (Exception e) { + LOGGER.error("NetworkException: " + getPlayer(), e); + close(new ChatMessage("disconnect.genericReason", "Internal Exception: " + e.getMessage()));; + } + }; } + this.channel.eventLoop().execute(choice1 != null ? choice1 : choice2); + // Tuinity end - optimise packets that are not flushed } } + private void a(final Packet packet, final GenericFutureListener>[] agenericfuturelistener) { + this.dispatchPacket(packet, agenericfuturelistener, Boolean.TRUE); + } + + // eSpigot start - fast packets processing + private final Object fastPacketsLock = new Object(); + public void processFastPackets() { if (this.fastPackets.isEmpty()) // Don't lock return; @@ -349,10 +318,10 @@ public class NetworkManager extends SimpleChannelInboundHandler { Iterator iterator = this.fastPackets.iterator(); while (iterator.hasNext()) { QueuedPacket queued = iterator.next(); - Packet packet = queued.a; + Packet packet = queued.a; if (hasWrotePacket && (needsFlush || this.canFlush)) flush(); iterator.remove(); - this.a(packet, queued.b, (!iterator.hasNext() && (needsFlush || this.canFlush)) ? Boolean.TRUE : Boolean.FALSE); + this.dispatchPacket(packet, queued.b, (!iterator.hasNext() && (needsFlush || this.canFlush)) ? Boolean.TRUE : Boolean.FALSE); hasWrotePacket = true; } } finally { @@ -361,9 +330,37 @@ public class NetworkManager extends SimpleChannelInboundHandler { } } } + // eSpigot end - public void a() { - this.m(); + private void sendPacketQueue() { + if(this.i.isEmpty()) return; // [Nacho-0019] :: Avoid lock every packet send + if (this.channel != null && this.channel.isOpen()) { + this.j.readLock().lock(); + boolean needsFlush = this.canFlush; + boolean hasWrotePacket = false; + try { + Iterator iterator = this.i.iterator(); + while (iterator.hasNext()) { + QueuedPacket queued = iterator.next(); + Packet packet = queued.a; + if (hasWrotePacket && (needsFlush || this.canFlush)) flush(); + iterator.remove(); + this.dispatchPacket(packet, queued.b, (!iterator.hasNext() && (needsFlush || this.canFlush)) ? Boolean.TRUE : Boolean.FALSE); + hasWrotePacket = true; + } + } finally { + this.j.readLock().unlock(); + } + } + } + + private void m() + { + this.sendPacketQueue(); + } + + public void tick() { + this.sendPacketQueue(); if (this.m instanceof IUpdatePlayerListBox) { ((IUpdatePlayerListBox) this.m).c(); } @@ -371,12 +368,16 @@ public class NetworkManager extends SimpleChannelInboundHandler { this.channel.flush(); } + public void a() { + this.tick(); + } + public SocketAddress getSocketAddress() { return this.l; } public void close(IChatBaseComponent ichatbasecomponent) { - this.i.clear(); // KigPaper + this.i.clear(); // FlamePaper - Minetick fix memory leaks // Spigot Start this.preparing = false; // Spigot End @@ -384,21 +385,43 @@ public class NetworkManager extends SimpleChannelInboundHandler { this.channel.close(); // We can't wait as this may be called from an event loop. this.n = ichatbasecomponent; } - } public boolean c() { return this.channel instanceof LocalChannel || this.channel instanceof LocalServerChannel; } - /* public void a(SecretKey secretkey) { + // Paper start + /* + public void setEncryptionKey(SecretKey secretkey) { this.o = true; this.channel.pipeline().addBefore("splitter", "decrypt", new PacketDecrypter(MinecraftEncryption.a(2, secretkey))); this.channel.pipeline().addBefore("prepender", "encrypt", new PacketEncrypter(MinecraftEncryption.a(1, secretkey))); - } */ + }*/ + + public void setupEncryption(javax.crypto.SecretKey key) throws CryptException { + if (!this.encrypted) { // Nacho - deobfuscate encrypted + try { + com.velocitypowered.natives.encryption.VelocityCipher decryption = com.velocitypowered.natives.util.Natives.cipher.get().forDecryption(key); + com.velocitypowered.natives.encryption.VelocityCipher encryption = com.velocitypowered.natives.util.Natives.cipher.get().forEncryption(key); + + this.encrypted = true; // Nacho - deobfuscate encrypted + this.channel.pipeline().addBefore("splitter", "decrypt", new PacketDecrypter(decryption)); + this.channel.pipeline().addBefore("prepender", "encrypt", new PacketEncrypter(encryption)); + } catch (java.security.GeneralSecurityException e) { + throw new CryptException(e); + } + } + } + // Paper end + + public boolean isConnected() + { + return this.channel != null && this.channel.isOpen(); + } public boolean g() { - return this.channel != null && this.channel.isOpen(); + return this.isConnected(); } public boolean h() { @@ -417,25 +440,17 @@ public class NetworkManager extends SimpleChannelInboundHandler { this.channel.config().setAutoRead(false); } - public void a(int i) - { - // Nacho start - OBFHELPER - this.setupCompression(i); - } - - public void setupCompression(int compressionThreshold) { - // Nacho end - if (compressionThreshold >= 0) - { + public void setupCompression(int compressionThreshold) { // Nacho - deobfuscate + if (compressionThreshold >= 0) { VelocityCompressor compressor = Natives.compress.get().create(-1); // Paper if (this.channel.pipeline().get("decompress") instanceof PacketDecompressor) { - ((PacketDecompressor) this.channel.pipeline().get("decompress")).a(compressionThreshold); + ((PacketDecompressor) this.channel.pipeline().get("decompress")).setThreshold(compressionThreshold); // Nacho - deobfuscate setThreshold } else { this.channel.pipeline().addBefore("decoder", "decompress", new PacketDecompressor(compressor, compressionThreshold)); // Paper } if (this.channel.pipeline().get("compress") instanceof PacketCompressor) { - ((PacketCompressor) this.channel.pipeline().get("decompress")).a(compressionThreshold); + ((PacketCompressor) this.channel.pipeline().get("decompress")).setThreshold(compressionThreshold); // Nacho - deobfuscate setThreshold } else { this.channel.pipeline().addBefore("encoder", "compress", new PacketCompressor(compressor, compressionThreshold)); // Paper } @@ -450,10 +465,13 @@ public class NetworkManager extends SimpleChannelInboundHandler { } } - public void l() { - if (this.channel != null && !this.channel.isOpen()) { - if (!this.p) { - this.p = true; + public void handleDisconnection() + { + if (this.channel != null && !this.channel.isOpen()) + { + if (!this.isDisconnectionHandled) // Nacho - deobfuscate isDisconnectionHandled + { + this.isDisconnectionHandled = true; // Nacho - deobfuscate isDisconnectionHandled if (this.j() != null) { this.getPacketListener().a(this.j()); } else if (this.getPacketListener() != null) { @@ -461,33 +479,40 @@ public class NetworkManager extends SimpleChannelInboundHandler { } this.i.clear(); // Free up packet queue. } else { - NetworkManager.g.warn("handleDisconnection() called twice"); + NetworkManager.LOGGER.warn("handleDisconnection() called twice"); } } } + public void l() + { + this.handleDisconnection(); + } + + @Override protected void channelRead0(ChannelHandlerContext channelhandlercontext, Packet object) throws Exception { // CraftBukkit - fix decompile error + // this.a(channelhandlercontext, object); // FlamePaper - Check if channel is opened before reading packet if (g()) { this.a(channelhandlercontext, object); } } + static class QueuedPacket { + private final Packet a; //packet + private final GenericFutureListener>[] b; //listener + + @SafeVarargs + public QueuedPacket(Packet packet, GenericFutureListener> ...listeners) { + this.a = packet; + this.b = listeners; + } + } + // Spigot Start public SocketAddress getRawAddress() { return this.channel.remoteAddress(); } - - static class QueuedPacket { - - private final Packet a; - private final GenericFutureListener>[] b; - - public QueuedPacket(Packet packet, GenericFutureListener>... agenericfuturelistener) { - this.a = packet; - this.b = agenericfuturelistener; - } - } // Spigot End } \ No newline at end of file diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/PacketDecoder.java b/eSpigot-Server/src/main/java/net/minecraft/server/PacketDecoder.java index bbebe3a..f3ecbed 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/PacketDecoder.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/PacketDecoder.java @@ -13,33 +13,30 @@ import org.apache.logging.log4j.MarkerManager; public class PacketDecoder extends ByteToMessageDecoder { private static final Logger a = LogManager.getLogger(); - private static final Marker b = MarkerManager.getMarker("PACKET_RECEIVED", NetworkManager.b); + private static final Marker b; private final EnumProtocolDirection c; public PacketDecoder(EnumProtocolDirection enumprotocoldirection) { this.c = enumprotocoldirection; } - protected void decode(ChannelHandlerContext channelhandlercontext, ByteBuf bytebuf, List list) throws Exception { - if (bytebuf.readableBytes() != 0) { - PacketDataSerializer packetdataserializer = new PacketDataSerializer(bytebuf); - int i = packetdataserializer.e(); - Packet packet = ((EnumProtocol) channelhandlercontext.channel().attr(NetworkManager.c).get()).a(this.c, i); + protected void decode(ChannelHandlerContext ctx, ByteBuf bytebuf, List list) throws Exception { + if (!bytebuf.isReadable()) return; - if (packet == null) { - throw new IOException("Bad packet id " + i); - } else { - packet.a(packetdataserializer); - if (packetdataserializer.readableBytes() > 0) { - throw new IOException("Packet " + ((EnumProtocol) channelhandlercontext.channel().attr(NetworkManager.c).get()).a() + "/" + i + " (" + packet.getClass().getSimpleName() + ") was larger than I expected, found " + packetdataserializer.readableBytes() + " bytes extra whilst reading packet " + i); - } else { - list.add(packet); - if (PacketDecoder.a.isDebugEnabled()) { - PacketDecoder.a.debug(PacketDecoder.b, " IN: [{}:{}] {}", new Object[] { channelhandlercontext.channel().attr(NetworkManager.c).get(), Integer.valueOf(i), packet.getClass().getName()}); - } + PacketDataSerializer packetDataHelper = new PacketDataSerializer(bytebuf); + int packetId = packetDataHelper.e(); + Packet packet = ctx.channel().attr(NetworkManager.ATTRIBUTE_PROTOCOL).get().a(this.c, packetId); + if (packet == null) + throw new IOException("Bad packet id " + packetId); - } - } - } + packet.a(packetDataHelper); + + if (packetDataHelper.isReadable()) + throw new IOException("Packet " + ctx.channel().attr(NetworkManager.ATTRIBUTE_PROTOCOL).get().getStateId() + "/" + packetId + " (" + packet.getClass().getSimpleName() + ") was larger than I expected, found " + packetDataHelper.readableBytes() + " bytes extra whilst reading packet " + packetId); + list.add(packet); + } + + static { + b = MarkerManager.getMarker("PACKET_RECEIVED", NetworkManager.PACKET_MARKER); } } diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/PacketDecompressor.java b/eSpigot-Server/src/main/java/net/minecraft/server/PacketDecompressor.java index 9e8f6af..e17fb8c 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/PacketDecompressor.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/PacketDecompressor.java @@ -78,4 +78,8 @@ public class PacketDecompressor extends ByteToMessageDecoder { public void a(int var1) { this.threshold = var1; } + + public void setThreshold(int var1) { // Nacho - deobfuscate + a(var1); + } } \ No newline at end of file diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/PacketEncoder.java b/eSpigot-Server/src/main/java/net/minecraft/server/PacketEncoder.java index 58214e1..a924e94 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/PacketEncoder.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/PacketEncoder.java @@ -1,52 +1,44 @@ package net.minecraft.server; +import com.elevatemc.spigot.exception.ExploitException; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; import java.io.IOException; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.Marker; -import org.apache.logging.log4j.MarkerManager; -public class PacketEncoder extends MessageToByteEncoder { +public class PacketEncoder extends MessageToByteEncoder> { - private static final Logger a = LogManager.getLogger(); - private static final Marker b = MarkerManager.getMarker("PACKET_SENT", NetworkManager.b); + // private static final Logger a = LogManager.getLogger(); + // private static final Marker b = MarkerManager.getMarker("PACKET_SENT", NetworkManager.PACKET_MARKER); private final EnumProtocolDirection c; public PacketEncoder(EnumProtocolDirection enumprotocoldirection) { this.c = enumprotocoldirection; } - protected void a(ChannelHandlerContext channelhandlercontext, Packet packet, ByteBuf bytebuf) throws Exception { - Integer integer = ((EnumProtocol) channelhandlercontext.channel().attr(NetworkManager.c).get()).a(this.c, packet); + protected void a(ChannelHandlerContext ctx, Packet packet, ByteBuf bytebuf) throws Exception { + Integer packetId = (ctx.channel().attr(NetworkManager.c).get()).a(this.c, packet); - if (PacketEncoder.a.isDebugEnabled()) { - PacketEncoder.a.debug(PacketEncoder.b, "OUT: [{}:{}] {}", new Object[] { channelhandlercontext.channel().attr(NetworkManager.c).get(), integer, packet.getClass().getName()}); - } + /*if (PacketEncoder.a.isDebugEnabled()) { + PacketEncoder.a.debug(PacketEncoder.b, "OUT: [{}:{}] {}", ctx.channel().attr(NetworkManager.ATTRIBUTE_PROTOCOL).get(), packetId, packet.getClass().getName()); + }*/ - if (integer == null) { - throw new IOException("Can\'t serialize unregistered packet"); + if (packetId == null) { + throw new IOException("Can't serialize unregistered packet"); } else { - PacketDataSerializer packetdataserializer = new PacketDataSerializer(bytebuf); - - packetdataserializer.b(integer.intValue()); + PacketDataSerializer serializer = new PacketDataSerializer(bytebuf); + serializer.b(packetId); // Nacho - deobfuscate writeVarInt try { - if (packet instanceof PacketPlayOutNamedEntitySpawn) { - packet = packet; - } - - packet.b(packetdataserializer); - } catch (Throwable throwable) { - PacketEncoder.a.error(throwable); + packet.b(serializer); + } catch (ExploitException ex) { + MinecraftServer.LOGGER.error("Exploit exception: " + ctx.channel().attr(NetworkManager.ATTRIBUTE_PROTOCOL).get()); } - } } - protected void encode(ChannelHandlerContext channelhandlercontext, Packet object, ByteBuf bytebuf) throws Exception { - this.a(channelhandlercontext, object, bytebuf); + @Override + protected void encode(ChannelHandlerContext channelHandlerContext, Packet packet, ByteBuf byteBuf) throws Exception { + this.a(channelHandlerContext, packet, byteBuf); } -} +} \ No newline at end of file diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/PlayerList.java b/eSpigot-Server/src/main/java/net/minecraft/server/PlayerList.java index dbfaee6..938d41d 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/PlayerList.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/PlayerList.java @@ -53,7 +53,6 @@ public abstract class PlayerList { private final GameProfileBanList k; private final IpBanList l; private final OpList operators; - private Set fastOperator = new HashSet<>(); private final WhiteList whitelist; private final Map o; public IPlayerFileData playerFileData; @@ -76,9 +75,6 @@ public abstract class PlayerList { this.k = new GameProfileBanList(PlayerList.a); this.l = new IpBanList(PlayerList.b); this.operators = new OpList(PlayerList.c); - for (OpListEntry value : this.operators.getValues()) { - this.fastOperator.add(value.getKey().getId()); - } this.whitelist = new WhiteList(PlayerList.d); this.o = Maps.newHashMap(); this.server = minecraftserver; @@ -1020,7 +1016,6 @@ public abstract class PlayerList { public void addOp(GameProfile gameprofile) { this.operators.add(new OpListEntry(gameprofile, this.server.p(), this.operators.b(gameprofile))); - this.fastOperator.add(gameprofile.getId()); // CraftBukkit start Player player = server.server.getPlayer(gameprofile.getId()); @@ -1032,7 +1027,6 @@ public abstract class PlayerList { public void removeOp(GameProfile gameprofile) { this.operators.remove(gameprofile); - this.fastOperator.remove(gameprofile.getId()); // CraftBukkit start Player player = server.server.getPlayer(gameprofile.getId()); @@ -1043,11 +1037,11 @@ public abstract class PlayerList { } public boolean isWhitelisted(GameProfile gameprofile) { - return !this.hasWhitelist || this.fastOperator.contains(gameprofile.getId()) || this.whitelist.d(gameprofile); + return !this.hasWhitelist || this.operators.d(gameprofile) || this.whitelist.d(gameprofile); } public boolean isOp(GameProfile gameprofile) { - return this.fastOperator.contains(gameprofile.getId()) || this.server.T() && this.server.worlds.get(0).getWorldData().v() && this.server.S().equalsIgnoreCase(gameprofile.getName()) || this.t; // CraftBukkit + return this.operators.d(gameprofile) || this.server.T() && this.server.worlds.get(0).getWorldData().v() && this.server.S().equalsIgnoreCase(gameprofile.getName()) || this.t; // CraftBukkit } public EntityPlayer getPlayer(String s) { diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/ServerConnection.java b/eSpigot-Server/src/main/java/net/minecraft/server/ServerConnection.java index 1065937..b255dd9 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/ServerConnection.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/ServerConnection.java @@ -1,197 +1,189 @@ package net.minecraft.server; +import com.elevatemc.spigot.network.MinecraftPipeline; import com.google.common.collect.Lists; -import com.google.common.util.concurrent.ThreadFactoryBuilder; -import com.velocitypowered.natives.util.Natives; +import com.velocitypowered.natives.util.Natives; // Paper import io.netty.bootstrap.ServerBootstrap; -import io.netty.buffer.PooledByteBufAllocator; import io.netty.channel.*; import io.netty.channel.epoll.Epoll; import io.netty.channel.epoll.EpollEventLoopGroup; import io.netty.channel.epoll.EpollServerSocketChannel; -import io.netty.channel.local.LocalEventLoopGroup; +import io.netty.channel.kqueue.KQueue; +import io.netty.channel.kqueue.KQueueEventLoopGroup; +import io.netty.channel.kqueue.KQueueServerSocketChannel; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; -import io.netty.handler.timeout.ReadTimeoutHandler; -import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; import java.io.IOException; import java.net.InetAddress; -import java.util.ArrayList; +import java.net.SocketAddress; import java.util.Collections; import java.util.Iterator; import java.util.List; -import java.util.concurrent.Callable; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.github.paperspigot.PaperSpigotConfig; public class ServerConnection { - private static final Logger e = LogManager.getLogger(); - public static final LazyInitVar a = new LazyInitVar() { - protected NioEventLoopGroup a() { - return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Server IO #%d").setDaemon(true).build()); - } - - protected Object init() { - return this.a(); - } - }; - public static final LazyInitVar b = new LazyInitVar() { - protected EpollEventLoopGroup a() { - return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Server IO #%d").setDaemon(true).build()); - } - - protected Object init() { - return this.a(); - } - }; - public static final LazyInitVar c = new LazyInitVar() { - protected LocalEventLoopGroup a() { - return new LocalEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Local Server IO #%d").setDaemon(true).build()); - } - - protected Object init() { - return this.a(); - } - }; - private final MinecraftServer f; - public volatile boolean d; - private final List g = Collections.synchronizedList(Lists.newArrayList()); - private final List h = Collections.synchronizedList(Lists.newArrayList()); - - public ServerConnection(MinecraftServer minecraftserver) { - this.f = minecraftserver; - this.d = true; + public enum EventGroupType { + EPOLL, + KQUEUE, + NIO, + DEFAULT } + private static final WriteBufferWaterMark SERVER_WRITE_MARK = new WriteBufferWaterMark(1 << 20, 1 << 21); + + private static final Logger LOGGER = LogManager.getLogger(); + + private final EventGroupType eventGroupType; + + public static EventLoopGroup boss, worker; + + public final MinecraftServer server; + public volatile boolean started; + + private final List listeningChannels = Collections.synchronizedList(Lists.newArrayList()); + + private final List connectedChannels = Collections.synchronizedList(Lists.newArrayList()); + // Paper start - prevent blocking on adding a new network manager while the server is ticking - private final List pending = Collections.synchronizedList(Lists.newArrayList()); + public final java.util.Queue pending = new java.util.concurrent.ConcurrentLinkedQueue<>(); private void addPending() { - synchronized (pending) { - this.h.addAll(pending); - pending.clear(); + NetworkManager manager; + while ((manager = pending.poll()) != null) { + this.connectedChannels.add(manager); // Nacho - deobfuscate connectedChannels } } // Paper end - // PandaSpigot start - public void a(InetAddress inetaddress, int i) throws IOException { - bind(new java.net.InetSocketAddress(inetaddress, i)); + + public ServerConnection(MinecraftServer server) { + this.server = server; + this.started = true; + + if (server.ai()) /* use-native-transport */ { + if (Epoll.isAvailable()) { + this.eventGroupType = EventGroupType.EPOLL; + return; + } else if (KQueue.isAvailable()) { + this.eventGroupType = EventGroupType.KQUEUE; + return; + } + } + + this.eventGroupType = server.getTransport(); } - public void bind(java.net.SocketAddress address) throws IOException { - // PandaSpigot end - List list = this.g; - synchronized (this.g) { - Class oclass; - LazyInitVar lazyinitvar; + public void a(SocketAddress ip, int port) throws IOException { + synchronized (this.listeningChannels) { // Nacho - deobfuscate listeningChannels + Class channel = null; + final int workerThreadCount = Runtime.getRuntime().availableProcessors(); - try { - if (Epoll.isAvailable() && this.f.ai()) { - // PandaSpigot start - Unix domain socket support - if (address instanceof io.netty.channel.unix.DomainSocketAddress) { - oclass = io.netty.channel.epoll.EpollServerDomainSocketChannel.class; - } else { - oclass = EpollServerSocketChannel.class; + { + switch (eventGroupType) { + default: + case DEFAULT: { + LOGGER.info("Finding best event group type using fall-through"); + } + + case EPOLL: { + if (Epoll.isAvailable()) { + boss = new EpollEventLoopGroup(0); + worker = new EpollEventLoopGroup(workerThreadCount); + + // PandaSpigot start - Unix domain socket support + if (ip instanceof io.netty.channel.unix.DomainSocketAddress) { + channel = io.netty.channel.epoll.EpollServerDomainSocketChannel.class; + } else { + channel = EpollServerSocketChannel.class; + } + + LOGGER.info("Using epoll"); + + break; + } + } + case KQUEUE: { + if (KQueue.isAvailable()) { + boss = new KQueueEventLoopGroup(0); + worker = new KQueueEventLoopGroup(workerThreadCount); + + channel = KQueueServerSocketChannel.class; + + LOGGER.info("Using kqueue"); + + break; + } + } + case NIO: { + boss = new NioEventLoopGroup(0); + worker = new NioEventLoopGroup(workerThreadCount); + + channel = NioServerSocketChannel.class; + + LOGGER.info("Using NIO"); + + break; } - // PandaSpigot end - lazyinitvar = ServerConnection.b; - ServerConnection.e.info("Using epoll channel type"); - } else { - oclass = NioServerSocketChannel.class; - lazyinitvar = ServerConnection.a; - ServerConnection.e.info("Using default channel type"); } - } catch (Exception e) { - ServerConnection.e.warn("An error occurred trying to check if Epoll is available, falling back to default channel type."); - oclass = NioServerSocketChannel.class; - lazyinitvar = ServerConnection.a; - ServerConnection.e.info("Using default channel type"); } - // Paper start - indicate Velocity natives in use - MinecraftServer.LOGGER.info("Using " + Natives.compress.getLoadedVariant() + " compression from Velocity."); - MinecraftServer.LOGGER.info("Using " + Natives.cipher.getLoadedVariant() + " cipher from Velocity."); - // Paper end + // Paper/Nacho start - indicate Velocity natives in use + LOGGER.info("Using " + Natives.compress.getLoadedVariant() + " compression from Velocity."); + LOGGER.info("Using " + Natives.cipher.getLoadedVariant() + " cipher from Velocity."); + // Paper/Nacho end - this.g.add((new ServerBootstrap()).channel(oclass).childHandler(new ChannelInitializer() { - protected void initChannel(Channel channel) throws Exception { - // KigPaper start - if(PaperSpigotConfig.nettyReadTimeout) channel.pipeline().addLast("timeout", new ReadTimeoutHandler(30)); - // KigPaper end - // PandaSpigot start - newlines - channel.pipeline() - .addLast("legacy_query", new LegacyPingHandler(ServerConnection.this)) - .addLast("splitter", new PacketSplitter()) - .addLast("decoder", new PacketDecoder(EnumProtocolDirection.SERVERBOUND)) - .addLast("prepender", PacketPrepender.INSTANCE) // PandaSpigot - Share PacketPrepender instance - .addLast("encoder", new PacketEncoder(EnumProtocolDirection.CLIENTBOUND)); - // PandaSpigot end - NetworkManager networkmanager = new NetworkManager(EnumProtocolDirection.SERVERBOUND); - - //ServerConnection.this.h.add(networkmanager); - pending.add(networkmanager); // Paper - channel.pipeline().addLast("packet_handler", networkmanager); - networkmanager.a(new HandshakeListener(ServerConnection.this.f, networkmanager)); - } - }).group((EventLoopGroup) lazyinitvar.c()).localAddress(address).bind().syncUninterruptibly()); + this.listeningChannels.add(((new ServerBootstrap() // Nacho - deobfuscate listeningChannels + .channel(channel)) + .childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, SERVER_WRITE_MARK) + .childHandler(new MinecraftPipeline(this)) + .group(boss, worker) + .localAddress(ip)) + .bind() + .syncUninterruptibly()); } } - public void b() { - this.d = false; - - // CraftBukkit start - handle processQueue while closing channels to prevent deadlock - ArrayList futures = new ArrayList(); - - for (ChannelFuture channelfuture : this.g) { - futures.add(channelfuture.channel().close()); - } - - for(;;) { - futures.removeIf(java.util.concurrent.Future::isDone); - f.processTasks(); - - if(futures.isEmpty()) break; - + public void stopServer() throws InterruptedException { + this.started = false; + LOGGER.info("Shutting down event loops"); + for (ChannelFuture future : this.listeningChannels) { // Nacho - deobfuscate listeningChannels try { - Thread.sleep(50); - } catch(InterruptedException e) { - e.printStackTrace(); + future.channel().close().sync(); + } finally { + boss.shutdownGracefully(); + worker.shutdownGracefully(); } } - // CraftBukkit end + } public void processFastPackets() { - synchronized (this.h) { - for (NetworkManager networkManager : this.h) { + synchronized (this.connectedChannels) { + for (NetworkManager networkManager : this.connectedChannels) { networkManager.processFastPackets(); } } } public void c() { - List list = this.h; - - synchronized (this.h) { + synchronized (this.connectedChannels) { // Nacho - deobfuscate connectedChannels // Spigot Start - addPending(); // KigPaper + this.addPending(); // Paper // This prevents players from 'gaming' the server, and strategically relogging to increase their position in the tick order - if ( org.spigotmc.SpigotConfig.playerShuffle > 0 && MinecraftServer.currentTick % org.spigotmc.SpigotConfig.playerShuffle == 0 ) - { - Collections.shuffle( this.h ); + if ( org.spigotmc.SpigotConfig.playerShuffle > 0 && MinecraftServer.currentTick % org.spigotmc.SpigotConfig.playerShuffle == 0 ) { + Collections.shuffle( this.connectedChannels); // Nacho - deobfuscate connectedChannels } // Spigot End - Iterator iterator = this.h.iterator(); + Iterator iterator = this.connectedChannels.iterator(); // Nacho - deobfuscate connectedChannels while (iterator.hasNext()) { - final NetworkManager networkmanager = (NetworkManager) iterator.next(); + final NetworkManager networkmanager = iterator.next(); if (!networkmanager.h()) { - if (!networkmanager.g()) { + if (!networkmanager.isConnected()) { // Spigot Start // Fix a race condition where a NetworkManager could be unregistered just before connection. if (networkmanager.preparing) continue; @@ -200,25 +192,17 @@ public class ServerConnection { networkmanager.l(); } else { try { - networkmanager.a(); + networkmanager.tick(); } catch (Exception exception) { if (networkmanager.c()) { CrashReport crashreport = CrashReport.a(exception, "Ticking memory connection"); CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Ticking connection"); - crashreportsystemdetails.a("Connection", new Callable() { - public String a() throws Exception { - return networkmanager.toString(); - } - - public Object call() throws Exception { - return this.a(); - } - }); + crashreportsystemdetails.a("Connection", networkmanager::toString); throw new ReportedException(crashreport); } - ServerConnection.e.warn("Failed to handle packet for " + networkmanager.getSocketAddress(), exception); + ServerConnection.LOGGER.warn("Failed to handle packet for " + networkmanager.getSocketAddress(), exception); final ChatComponentText chatcomponenttext = new ChatComponentText("Internal server error"); networkmanager.a(new PacketPlayOutKickDisconnect(chatcomponenttext), (GenericFutureListener) future -> networkmanager.close(chatcomponenttext), new GenericFutureListener[0]); @@ -232,6 +216,6 @@ public class ServerConnection { } public MinecraftServer d() { - return this.f; + return this.server; } -} +} \ No newline at end of file diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/World.java b/eSpigot-Server/src/main/java/net/minecraft/server/World.java index 46238c1..4778745 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/World.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/World.java @@ -1,9 +1,12 @@ package net.minecraft.server; +import com.elevatemc.spigot.config.eSpigotConfig; +import com.elevatemc.spigot.util.OptimizedWorldTileEntitySet; import com.google.common.base.Predicate; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; +import net.jafama.FastMath; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.block.BlockState; @@ -63,7 +66,7 @@ public abstract class World implements IBlockAccess { // Spigot end protected final Set g = Sets.newHashSet(); // Paper //public final List h = Lists.newArrayList(); // PaperSpigot - Remove unused list - public final List tileEntityList = Lists.newArrayList(); + public final OptimizedWorldTileEntitySet tileEntityList = new OptimizedWorldTileEntitySet(); private final List b = Lists.newArrayList(); private final Set c = Sets.newHashSet(); // Paper public Set getTileEntityListUnload() { @@ -140,7 +143,12 @@ public abstract class World implements IBlockAccess { private org.spigotmc.TickLimiter tileLimiter; private int tileTickPosition; public ExecutorService lightingExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("PaperSpigot - Lighting Thread").build()); // PaperSpigot - Asynchronous lighting updates - public final Map explosionDensityCache = new HashMap<>(); // PaperSpigot - Optimize explosions + // IonSpigot start - Optimise Density Cache + public final it.unimi.dsi.fastutil.ints.Int2FloatMap explosionDensityCache = new it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap(); // IonSpigot - Use faster collection here // PaperSpigot - Optimize explosions + { + explosionDensityCache.defaultReturnValue(-1.0f); + } + // IonSpigot end public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Move from Map in BlockRedstoneTorch to here public static long chunkToKey(int x, int z) @@ -1419,6 +1427,7 @@ public abstract class World implements IBlockAccess { } // Spigot end + if (!eSpigotConfig.entityCollisions) return arraylist; if (entity instanceof EntityItem) return arraylist; // PaperSpigot - Optimize item movement if (entity instanceof EntityArmorStand && !entity.world.paperSpigotConfig.armorStandEntityLookups) return arraylist; // Paper if (entity instanceof EntityArmorStand) return arraylist; // TacoSpigot - Optimize armor stand movement @@ -1768,14 +1777,15 @@ public abstract class World implements IBlockAccess { // Spigot start int tilesThisCycle = 0; - for (tileTickPosition = 0; tileTickPosition < tileEntityList.size(); tileTickPosition++) { // PaperSpigot - Disable tick limiters + Iterator tileIterator = this.tileEntityList.tickIterator(this.getTime()); + while (tileIterator.hasNext()) { // PaperSpigot - Disable tick limiters tileTickPosition = (tileTickPosition < tileEntityList.size()) ? tileTickPosition : 0; - TileEntity tileentity = this.tileEntityList.get(tileTickPosition); + TileEntity tileentity = (TileEntity) tileIterator.next(); // Spigot start if (tileentity == null) { getServer().getLogger().severe("Spigot has detected a null entity and has removed it, preventing a crash"); tilesThisCycle--; - this.tileEntityList.remove(tileTickPosition--); + tileIterator.remove(); continue; } // Spigot end @@ -1785,15 +1795,21 @@ public abstract class World implements IBlockAccess { if (this.isLoaded(blockposition) && this.N.a(blockposition)) { try { + // Nacho start - Fix mob spawners still spawning mobs after being broken + if (this.getTileEntity(tileentity.getPosition()) == null){ + tileIterator.remove(); + continue; + } + // Nacho end tileentity.tickTimer.startTiming(); // Spigot ((IUpdatePlayerListBox) tileentity).c(); } catch (Throwable throwable2) { // PaperSpigot start - Prevent tile entity and entity crashes tileentity.tickTimer.stopTiming(); - System.err.println("TileEntity threw exception at " + tileentity.world.getWorld().getName() + ":" + tileentity.position.getX() + "," + tileentity.position.getY() + "," + tileentity.position.getZ()); + MinecraftServer.LOGGER.error("TileEntity threw exception at " + tileentity.world.getWorld().getName() + ":" + tileentity.position.getX() + "," + tileentity.position.getY() + "," + tileentity.position.getZ()); throwable2.printStackTrace(); tilesThisCycle--; - this.tileEntityList.remove(tileTickPosition--); + tileIterator.remove(); continue; // PaperSpigot end } @@ -1807,7 +1823,7 @@ public abstract class World implements IBlockAccess { if (tileentity.x()) { tilesThisCycle--; - this.tileEntityList.remove(tileTickPosition--); + tileIterator.remove(); //this.h.remove(tileentity); // PaperSpigot - Remove unused list if (this.isLoaded(tileentity.getPosition())) { this.getChunkAtWorldCoords(tileentity.getPosition()).e(tileentity.getPosition()); @@ -2270,8 +2286,8 @@ public abstract class World implements IBlockAccess { double d0 = 1.0D / ((axisalignedbb.d - axisalignedbb.a) * 2.0D + 1.0D); double d1 = 1.0D / ((axisalignedbb.e - axisalignedbb.b) * 2.0D + 1.0D); double d2 = 1.0D / ((axisalignedbb.f - axisalignedbb.c) * 2.0D + 1.0D); - double d3 = (1.0D - Math.floor(1.0D / d0) * d0) / 2.0D; - double d4 = (1.0D - Math.floor(1.0D / d2) * d2) / 2.0D; + double d3 = (1.0D - ((eSpigotConfig.fastMath ? FastMath.floor(1.0D / d0) : Math.floor(1.0D / d0)) * d0)) / 2.0D; + double d4 = (1.0D - ((eSpigotConfig.fastMath ? FastMath.floor(1.0D / d2) : Math.floor(1.0D / d2)) * d2)) / 2.0D; if (d0 >= 0.0D && d1 >= 0.0D && d2 >= 0.0D) { int i = 0; @@ -2947,6 +2963,29 @@ public abstract class World implements IBlockAccess { return null; } + // IonSpigot start - Optimise Entity Collisions + public List getEntitiesByAmount(Entity entity, AxisAlignedBB axisalignedbb, Predicate by, int amount) { + List entities = new ArrayList<>(); + + int i = MathHelper.floor((axisalignedbb.a - 2.0D) / 16.0D); + int j = MathHelper.floor((axisalignedbb.d + 2.0D) / 16.0D); + int k = MathHelper.floor((axisalignedbb.c - 2.0D) / 16.0D); + int l = MathHelper.floor((axisalignedbb.f + 2.0D) / 16.0D); + + for (int i1 = i; i1 <= j; ++i1) { + for (int j1 = k; j1 <= l; ++j1) { + if (this.isChunkLoaded(i1, j1, true)) { + if (this.getChunkAt(i1, j1).collectEntitiesByAmount(entity, axisalignedbb, entities, by, amount)) { + return entities; + } + } + } + } + + return entities; + } + // IonSpigot end + public List getEntities(Entity entity, AxisAlignedBB axisalignedbb) { return this.a(entity, axisalignedbb, IEntitySelector.d); } diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/WorldNBTStorage.java b/eSpigot-Server/src/main/java/net/minecraft/server/WorldNBTStorage.java index 9d37f24..845ba10 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/WorldNBTStorage.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/WorldNBTStorage.java @@ -41,6 +41,13 @@ public class WorldNBTStorage implements IDataManager, IPlayerFileData { } this.h(); + + // manually check lock on startup + try { + checkSession0(); + } catch (Throwable t) { + org.spigotmc.SneakyThrow.sneaky(t); + } } private void h() { @@ -61,7 +68,9 @@ public class WorldNBTStorage implements IDataManager, IPlayerFileData { return this.baseDir; } - public void checkSession() throws ExceptionWorldConflict { + public void checkSession() throws ExceptionWorldConflict {} // CraftBukkit - throws ExceptionWorldConflict + + private void checkSession0() throws ExceptionWorldConflict { // we can safely do so as the server will stop upon detecting a session conflict on startup try { File file = new File(this.baseDir, "session.lock"); diff --git a/eSpigot-Server/src/main/java/net/minecraft/server/WorldServer.java b/eSpigot-Server/src/main/java/net/minecraft/server/WorldServer.java index 43efd56..11cbf78 100644 --- a/eSpigot-Server/src/main/java/net/minecraft/server/WorldServer.java +++ b/eSpigot-Server/src/main/java/net/minecraft/server/WorldServer.java @@ -347,32 +347,34 @@ public class WorldServer extends World implements IAsyncTaskHandler { } public boolean everyoneDeeplySleeping() { - if (this.O && !this.isClientSide) { - Iterator iterator = this.players.iterator(); + // PaperBin start - WorldServer#everyoneDeeplySleeping optimization + if (this.players.isEmpty() || this.isClientSide || !this.O) return false; + for (EntityHuman player : this.players) { + if (!player.isSpectator() && !player.isDeeplySleeping() && !player.fauxSleeping) { + return false; + } + } + return true; + // PaperBin end + /*if (this.O && !this.isClientSide) { + Iterator iterator = this.players.iterator(); // CraftBukkit - This allows us to assume that some people are in bed but not really, allowing time to pass in spite of AFKers boolean foundActualSleepers = false; - EntityHuman entityhuman; - do { if (!iterator.hasNext()) { return foundActualSleepers; } - - entityhuman = (EntityHuman) iterator.next(); - + entityhuman = iterator.next(); // CraftBukkit start if (entityhuman.isDeeplySleeping()) { foundActualSleepers = true; } } while (!entityhuman.isSpectator() || entityhuman.isDeeplySleeping() || entityhuman.fauxSleeping); // CraftBukkit end - - return false; - } else { - return false; } + return false;*/ } protected void h() { @@ -422,6 +424,7 @@ public class WorldServer extends World implements IAsyncTaskHandler { this.a(k, l, chunk); this.methodProfiler.c("tickChunk"); + if (!chunk.areNeighborsLoaded(1)) continue; // Spigot chunk.b(false); this.methodProfiler.c("thunder"); int i1; @@ -658,7 +661,7 @@ public class WorldServer extends World implements IAsyncTaskHandler { break; } - if (next.a().isPowerSource() || next.a() instanceof IContainer) { + if (next.a().isPowerSource() || next.a() instanceof IContainer || next.a() instanceof BlockFalling) { // IonSpigot - falling blocks should bypass tick cap iterator.remove(); this.V.add(next); } diff --git a/eSpigot-Server/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/eSpigot-Server/src/main/java/org/bukkit/craftbukkit/CraftServer.java index d9e81ba..c2f4b3d 100644 --- a/eSpigot-Server/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/eSpigot-Server/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -793,7 +793,7 @@ public final class CraftServer implements Server { } BlockPosition chunkcoordinates = internal.getSpawn(); - internal.chunkProviderServer.getChunkAt(chunkcoordinates.getX() + j >> 4, chunkcoordinates.getZ() + k >> 4); + internal.chunkProviderServer.getChunkAt(chunkcoordinates.getX() + j >> 4, chunkcoordinates.getZ() + k >> 4, () -> {}); // IonSpigot - Async Spawn Chunks } } } @@ -833,6 +833,9 @@ public final class CraftServer implements Server { return false; } + worlds.remove(world.getName().toLowerCase()); + console.worlds.remove(console.worlds.indexOf(handle)); + if (save) { try { handle.save(true, null); @@ -856,9 +859,6 @@ public final class CraftServer implements Server { // KigPaper end } - worlds.remove(world.getName().toLowerCase()); - console.worlds.remove(console.worlds.indexOf(handle)); - // KigPaper start - fix memory leak CraftingManager craftingManager = CraftingManager.getInstance(); CraftInventoryView lastView = (CraftInventoryView) craftingManager.lastCraftView; diff --git a/eSpigot-Server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/eSpigot-Server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index 5c1b0e2..9a93c76 100644 --- a/eSpigot-Server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/eSpigot-Server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -1,5 +1,6 @@ package org.bukkit.craftbukkit; +import com.elevatemc.spigot.config.eSpigotConfig; import com.elevatemc.spigot.util.Constants; import com.elevatemc.spigot.util.Dictionary; import com.google.common.base.Preconditions; @@ -14,6 +15,7 @@ import java.util.Random; import java.util.Set; import java.util.UUID; +import net.jafama.FastMath; import net.minecraft.server.*; import org.apache.commons.lang.Validate; @@ -415,23 +417,44 @@ public class CraftWorld implements World { double prevY = loc.getY(); double prevZ = loc.getZ(); loc.add(xs, ys, zs); - if (loc.getX() < Math.floor(prevX)) { - loc.setX(Math.floor(prevX)); - } - if (loc.getX() >= Math.ceil(prevX)) { - loc.setX(Math.ceil(prevX - 0.01)); - } - if (loc.getY() < Math.floor(prevY)) { - loc.setY(Math.floor(prevY)); - } - if (loc.getY() >= Math.ceil(prevY)) { - loc.setY(Math.ceil(prevY - 0.01)); - } - if (loc.getZ() < Math.floor(prevZ)) { - loc.setZ(Math.floor(prevZ)); - } - if (loc.getZ() >= Math.ceil(prevZ)) { - loc.setZ(Math.ceil(prevZ - 0.01)); + if (eSpigotConfig.fastMath) { + if (loc.getX() < FastMath.floor(prevX)) { + loc.setX(FastMath.floor(prevX)); + } + if (loc.getX() >= FastMath.ceil(prevX)) { + loc.setX(FastMath.ceil(prevX - 0.01)); + } + if (loc.getY() < FastMath.floor(prevY)) { + loc.setY(FastMath.floor(prevY)); + } + if (loc.getY() >= FastMath.ceil(prevY)) { + loc.setY(FastMath.ceil(prevY - 0.01)); + } + if (loc.getZ() < FastMath.floor(prevZ)) { + loc.setZ(FastMath.floor(prevZ)); + } + if (loc.getZ() >= Math.ceil(prevZ)) { + loc.setZ(FastMath.ceil(prevZ - 0.01)); + } + } else { + if (loc.getX() < Math.floor(prevX)) { + loc.setX(Math.floor(prevX)); + } + if (loc.getX() >= Math.ceil(prevX)) { + loc.setX(Math.ceil(prevX - 0.01)); + } + if (loc.getY() < Math.floor(prevY)) { + loc.setY(Math.floor(prevY)); + } + if (loc.getY() >= Math.ceil(prevY)) { + loc.setY(Math.ceil(prevY - 0.01)); + } + if (loc.getZ() < Math.floor(prevZ)) { + loc.setZ(Math.floor(prevZ)); + } + if (loc.getZ() >= Math.ceil(prevZ)) { + loc.setZ(Math.ceil(prevZ - 0.01)); + } } } diff --git a/eSpigot-Server/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java b/eSpigot-Server/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java index 4425c5c..4d40d49 100644 --- a/eSpigot-Server/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java +++ b/eSpigot-Server/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java @@ -15,7 +15,7 @@ public class ServerShutdownThread extends Thread { public void run() { try { server.stop(); - } catch (ExceptionWorldConflict ex) { + } catch (ExceptionWorldConflict | InterruptedException ex) { ex.printStackTrace(); } finally { try { diff --git a/eSpigot-Server/src/main/java/org/github/paperspigot/PaperSpigotWorldConfig.java b/eSpigot-Server/src/main/java/org/github/paperspigot/PaperSpigotWorldConfig.java index a6be096..2114e05 100644 --- a/eSpigot-Server/src/main/java/org/github/paperspigot/PaperSpigotWorldConfig.java +++ b/eSpigot-Server/src/main/java/org/github/paperspigot/PaperSpigotWorldConfig.java @@ -401,7 +401,7 @@ public class PaperSpigotWorldConfig } public static boolean isRedstoneFireBPE; - private void isRedstoneFireBPE() { + private static void isRedstoneFireBPE() { isRedstoneFireBPE = getBoolean("redstone-fire-BlockPhysicsEvent", true); } @@ -440,10 +440,24 @@ public class PaperSpigotWorldConfig } public static boolean fixEastWest; - private void fixEastWest() { + private static void fixEastWest() { fixEastWest = getBoolean("fix-east-west-cannoning", false); } public static boolean disableFallingBlockStackingAt256; - private void DisableFallingBlockStackingAt256() { disableFallingBlockStackingAt256 = getBoolean("disable-falling-block-stacking-at-256", false);} + private static void DisableFallingBlockStackingAt256() { + disableFallingBlockStackingAt256 = getBoolean("disable-falling-block-stacking-at-256", false); + } + + public static boolean constantExplosions; + public static boolean reducedDensityRays; + private static void explosions() { + constantExplosions = getBoolean("explosions.constant-radius", false); + reducedDensityRays = getBoolean("explosions.reduced-density-rays", false); + } + + public static boolean fixSandUnloading; + private static void fixSandUnloading() { + fixSandUnloading = getBoolean("sand.fix-unloading", false); + } } diff --git a/eSpigot-Server/src/main/java/org/spigotmc/Metrics.java b/eSpigot-Server/src/main/java/org/spigotmc/Metrics.java deleted file mode 100644 index 6f2c7cf..0000000 --- a/eSpigot-Server/src/main/java/org/spigotmc/Metrics.java +++ /dev/null @@ -1,631 +0,0 @@ -/* - * 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. - */ -package org.spigotmc; - -import org.bukkit.Bukkit; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.configuration.InvalidConfigurationException; - -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.UnsupportedEncodingException; -import java.net.Proxy; -import java.net.URL; -import java.net.URLConnection; -import java.net.URLEncoder; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.Timer; -import java.util.TimerTask; -import java.util.UUID; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import net.minecraft.server.MinecraftServer; - -/** - *

The metrics class obtains data about a plugin and submits statistics about it to the metrics backend.

- * Public methods provided by this class:

- * - * Graph createGraph(String name);
- * void addCustomData(BukkitMetrics.Plotter plotter);
- * void start();
- *
- */ -public class Metrics { - - /** - * The current revision number - */ - private final static int REVISION = 6; - /** - * The base url of the metrics domain - */ - private static final String BASE_URL = "http://mcstats.org"; - /** - * The url used to report a server's status - */ - private static final String REPORT_URL = "/report/%s"; - /** - * The separator to use for custom data. This MUST NOT change unless you are hosting your own version of metrics and - * want to change it. - */ - private static final String CUSTOM_DATA_SEPARATOR = "~~"; - /** - * Interval of time to ping (in minutes) - */ - private static final int PING_INTERVAL = 10; - /** - * All of the custom graphs to submit to metrics - */ - private final Set graphs = Collections.synchronizedSet(new HashSet<>()); - /** - * The default graph, used for addCustomData when you don't want a specific graph - */ - private final Graph defaultGraph = new Graph("Default"); - /** - * The plugin configuration file - */ - private final YamlConfiguration configuration; - /** - * The plugin configuration file - */ - private final File configurationFile; - /** - * Unique server id - */ - private final String guid; - /** - * Debug mode - */ - private final boolean debug; - /** - * Lock for synchronization - */ - private final Object optOutLock = new Object(); - /** - * The scheduled task - */ - private volatile Timer task = null; - - public Metrics() throws IOException { - // load the config - configurationFile = getConfigFile(); - configuration = YamlConfiguration.loadConfiguration(configurationFile); - - // add some defaults - configuration.addDefault("opt-out", false); - configuration.addDefault("guid", UUID.randomUUID().toString()); - configuration.addDefault("debug", false); - - // Do we need to create the file? - if (configuration.get("guid", null) == null) { - configuration.options().header("http://mcstats.org").copyDefaults(true); - configuration.save(configurationFile); - } - - // Load the guid then - guid = configuration.getString("guid"); - debug = configuration.getBoolean("debug", false); - } - - /** - * Construct and create a Graph that can be used to separate specific plotters to their own graphs on the metrics - * website. Plotters can be added to the graph object returned. - * - * @param name The name of the graph - * @return Graph object created. Will never return NULL under normal circumstances unless bad parameters are given - */ - public Graph createGraph(final String name) { - if (name == null) { - throw new IllegalArgumentException("Graph name cannot be null"); - } - - // Construct the graph object - final Graph graph = new Graph(name); - - // Now we can add our graph - graphs.add(graph); - - // and return back - return graph; - } - - /** - * Add a Graph object to BukkitMetrics that represents data for the plugin that should be sent to the backend - * - * @param graph The name of the graph - */ - public void addGraph(final Graph graph) { - if (graph == null) { - throw new IllegalArgumentException("Graph cannot be null"); - } - - graphs.add(graph); - } - - /** - * Adds a custom data plotter to the default graph - * - * @param plotter The plotter to use to plot custom data - */ - public void addCustomData(final Plotter plotter) { - if (plotter == null) { - throw new IllegalArgumentException("Plotter cannot be null"); - } - - // Add the plotter to the graph o/ - defaultGraph.addPlotter(plotter); - - // Ensure the default graph is included in the submitted graphs - graphs.add(defaultGraph); - } - - /** - * 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 - task = new Timer("Spigot Metrics Thread", true); - - task.scheduleAtFixedRate(new TimerTask() { - private boolean firstPost = true; - - 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; - // Tell all plotters to stop gathering information. - for (Graph graph : graphs) { - graph.onOptOut(); - } - } - } - - // 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 (IOException e) { - if (debug) { - Bukkit.getLogger().log(Level.INFO, "[Metrics] " + e.getMessage()); - } - } - } - }, 0, TimeUnit.MINUTES.toMillis(PING_INTERVAL)); - - return true; - } - } - - /** - * Has the server owner denied plugin metrics? - * - * @return true if metrics should be opted out of it - */ - public boolean isOptOut() { - synchronized (optOutLock) { - try { - // Reload the metrics file - configuration.load(getConfigFile()); - } catch (IOException | InvalidConfigurationException ex) { - if (debug) { - Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage()); - } - return true; - } - return configuration.getBoolean("opt-out", false); - } - } - - /** - * 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()) { - configuration.set("opt-out", false); - configuration.save(configurationFile); - } - - // 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()) { - configuration.set("opt-out", true); - configuration.save(configurationFile); - } - - // 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() { - // I believe the easiest way to get the base folder (e.g craftbukkit set via -P) for plugins to use - // is to abuse the plugin object we already have - // plugin.getDataFolder() => base/plugins/PluginA/ - // pluginsFolder => base/plugins/ - // The base is not necessarily relative to the startup directory. - // File pluginsFolder = plugin.getDataFolder().getParentFile(); - - // return => base/plugins/PluginMetrics/config.yml - return new File(new File((File) MinecraftServer.getServer().options.valueOf("plugins"), "PluginMetrics"), "config.yml"); - } - - /** - * Generic method that posts a plugin to the metrics website - */ - private void postPlugin(final boolean isPing) throws IOException { - // Server software specific section - String pluginName = "eSpigot"; // PaperSpigot - We need some usage data // TacoSpigot - its *my* usage data // eSpigot - boolean onlineMode = Bukkit.getServer().getOnlineMode(); // TRUE if online mode is enabled - String pluginVersion = (Metrics.class.getPackage().getImplementationVersion() != null) ? Metrics.class.getPackage().getImplementationVersion() : "unknown"; - String serverVersion = Bukkit.getVersion(); - int playersOnline = Bukkit.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 data = new StringBuilder(); - - // The plugin's description file containg all of the plugin data such as name, version, author, etc - data.append(encode("guid")).append('=').append(encode(guid)); - encodeDataPair(data, "version", pluginVersion); - encodeDataPair(data, "server", serverVersion); - encodeDataPair(data, "players", Integer.toString(playersOnline)); - encodeDataPair(data, "revision", String.valueOf(REVISION)); - - // New data as of R6 - String osname = System.getProperty("os.name"); - String osarch = System.getProperty("os.arch"); - String osversion = System.getProperty("os.version"); - String java_version = System.getProperty("java.version"); - int coreCount = Runtime.getRuntime().availableProcessors(); - - // normalize os arch .. amd64 -> x86_64 - if (osarch.equals("amd64")) { - osarch = "x86_64"; - } - - encodeDataPair(data, "osname", osname); - encodeDataPair(data, "osarch", osarch); - encodeDataPair(data, "osversion", osversion); - encodeDataPair(data, "cores", Integer.toString(coreCount)); - encodeDataPair(data, "online-mode", Boolean.toString(onlineMode)); - encodeDataPair(data, "java_version", java_version); - - // If we're pinging, append it - if (isPing) { - encodeDataPair(data, "ping", "true"); - } - - // Acquire a lock on the graphs, which lets us make the assumption we also lock everything - // inside of the graph (e.g plotters) - synchronized (graphs) { - - for (Graph graph : graphs) { - for (Plotter plotter : graph.getPlotters()) { - // The key name to send to the metrics server - // The format is C-GRAPHNAME-PLOTTERNAME where separator - is defined at the top - // Legacy (R4) submitters use the format Custom%s, or CustomPLOTTERNAME - final String key = String.format("C%s%s%s%s", CUSTOM_DATA_SEPARATOR, graph.getName(), CUSTOM_DATA_SEPARATOR, plotter.getColumnName()); - - // The value to send, which for the foreseeable future is just the string - // value of plotter.getValue() - final String value = Integer.toString(plotter.getValue()); - - // Add it to the http post data :) - encodeDataPair(data, key, value); - } - } - } - - // Create the url - URL url = new URL(BASE_URL + String.format(REPORT_URL, encode(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(); - } - - connection.setDoOutput(true); - - // Write the data - final OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream()); - writer.write(data.toString()); - writer.flush(); - - // Now read the response - final BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); - final String response = reader.readLine(); - - // close resources - writer.close(); - reader.close(); - - if (response == null || response.startsWith("ERR")) { - throw new IOException(response); //Throw the exception - } else { - // Is this the first update this hour? - if (response.contains("OK This is your first update this hour")) { - synchronized (graphs) { - - for (Graph graph : graphs) { - for (Plotter plotter : graph.getPlotters()) { - plotter.reset(); - } - } - } - } - } - } - - /** - * 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 (Exception e) { - return false; - } - } - - /** - *

Encode a key/value data pair to be used in a HTTP post request. This INCLUDES a & so the first key/value pair - * MUST be included manually, e.g:

- * - * StringBuffer data = new StringBuffer(); - * data.append(encode("guid")).append('=').append(encode(guid)); - * encodeDataPair(data, "version", description.getVersion()); - * - * - * @param buffer the stringbuilder to append the data pair onto - * @param key the key value - * @param value the value - */ - private static void encodeDataPair(final StringBuilder buffer, final String key, final String value) throws UnsupportedEncodingException { - buffer.append('&').append(encode(key)).append('=').append(encode(value)); - } - - /** - * Encode text as UTF-8 - * - * @param text the text to encode - * @return the encoded text, as UTF-8 - */ - private static String encode(final String text) throws UnsupportedEncodingException { - return URLEncoder.encode(text, "UTF-8"); - } - - /** - * Represents a custom graph on the website - */ - public static class Graph { - - /** - * The graph's name, alphanumeric and spaces only :) If it does not comply to the above when submitted, it is - * rejected - */ - private final String name; - /** - * The set of plotters that are contained within this graph - */ - private final Set plotters = new LinkedHashSet<>(); - - private Graph(final String name) { - this.name = name; - } - - /** - * Gets the graph's name - * - * @return the Graph's name - */ - public String getName() { - return name; - } - - /** - * Add a plotter to the graph, which will be used to plot entries - * - * @param plotter the plotter to add to the graph - */ - public void addPlotter(final Plotter plotter) { - plotters.add(plotter); - } - - /** - * Remove a plotter from the graph - * - * @param plotter the plotter to remove from the graph - */ - public void removePlotter(final Plotter plotter) { - plotters.remove(plotter); - } - - /** - * Gets an unmodifiable set of the plotter objects in the graph - * - * @return an unmodifiable {@link java.util.Set} of the plotter objects - */ - public Set getPlotters() { - return Collections.unmodifiableSet(plotters); - } - - @Override - public int hashCode() { - return name.hashCode(); - } - - @Override - public boolean equals(final Object object) { - if (!(object instanceof Graph)) { - return false; - } - - final Graph graph = (Graph) object; - return graph.name.equals(name); - } - - /** - * Called when the server owner decides to opt-out of BukkitMetrics while the server is running. - */ - protected void onOptOut() { - } - } - - /** - * Interface used to collect custom data for a plugin - */ - public static abstract class Plotter { - - /** - * The plot's name - */ - private final String name; - - /** - * Construct a plotter with the default plot name - */ - public Plotter() { - this("Default"); - } - - /** - * Construct a plotter with a specific plot name - * - * @param name the name of the plotter to use, which will show up on the website - */ - public Plotter(final String name) { - this.name = name; - } - - /** - * Get the current value for the plotted point. Since this function defers to an external function it may or may - * not return immediately thus cannot be guaranteed to be thread friendly or safe. This function can be called - * from any thread so care should be taken when accessing resources that need to be synchronized. - * - * @return the current value for the point to be plotted. - */ - public abstract int getValue(); - - /** - * Get the column name for the plotted point - * - * @return the plotted point's column name - */ - public String getColumnName() { - return name; - } - - /** - * Called after the website graphs have been updated - */ - public void reset() { - } - - @Override - public int hashCode() { - return getColumnName().hashCode(); - } - - @Override - public boolean equals(final Object object) { - if (!(object instanceof Plotter)) { - return false; - } - - final Plotter plotter = (Plotter) object; - return plotter.name.equals(name) && plotter.getValue() == getValue(); - } - } -} diff --git a/eSpigot-Server/src/main/java/org/spigotmc/RestartCommand.java b/eSpigot-Server/src/main/java/org/spigotmc/RestartCommand.java index 3e52830..8a85cef 100644 --- a/eSpigot-Server/src/main/java/org/spigotmc/RestartCommand.java +++ b/eSpigot-Server/src/main/java/org/spigotmc/RestartCommand.java @@ -58,7 +58,7 @@ public class RestartCommand extends Command { } // Close the socket so we can rebind with the new process - MinecraftServer.getServer().getServerConnection().b(); + MinecraftServer.getServer().getServerConnection().stopServer(); // Give time for it to kick in try