3
0

Initial commit (Forge 1291).

This commit is contained in:
gamerforEA
2015-03-22 20:38:04 +03:00
commit 16773ead6a
611 changed files with 64826 additions and 0 deletions

View File

@ -0,0 +1,327 @@
package org.spigotmc;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.entity.Entity;
import net.minecraft.entity.passive.EntityAmbientCreature;
import net.minecraft.entity.passive.EntityAnimal;
import net.minecraft.entity.projectile.EntityArrow;
import net.minecraft.entity.boss.EntityDragonPart;
import net.minecraft.entity.EntityCreature;
import net.minecraft.entity.item.EntityEnderCrystal;
import net.minecraft.entity.boss.EntityDragon;
import net.minecraft.entity.projectile.EntityFireball;
import net.minecraft.entity.item.EntityFireworkRocket;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.monster.EntityMob;
import net.minecraft.entity.projectile.EntityThrowable;
import net.minecraft.entity.passive.EntitySheep;
import net.minecraft.entity.monster.EntitySlime;
import net.minecraft.entity.item.EntityTNTPrimed;
import net.minecraft.entity.passive.EntityVillager;
import net.minecraft.entity.effect.EntityWeatherEffect;
import net.minecraft.entity.boss.EntityWither;
import net.minecraft.util.MathHelper;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.World;
import org.bukkit.craftbukkit.SpigotTimings;
// Cauldron start
import net.minecraft.entity.EnumCreatureType;
import net.minecraftforge.common.DimensionManager;
import net.minecraftforge.common.util.FakePlayer;
// Cauldron end
public class ActivationRange
{
static AxisAlignedBB maxBB = AxisAlignedBB.getBoundingBox( 0, 0, 0, 0, 0, 0 );
static AxisAlignedBB miscBB = AxisAlignedBB.getBoundingBox( 0, 0, 0, 0, 0, 0 );
static AxisAlignedBB animalBB = AxisAlignedBB.getBoundingBox( 0, 0, 0, 0, 0, 0 );
static AxisAlignedBB monsterBB = AxisAlignedBB.getBoundingBox( 0, 0, 0, 0, 0, 0 );
/**
* Initializes an entities type on construction to specify what group this
* entity is in for activation ranges.
*
* @param entity
* @return group id
*/
public static byte initializeEntityActivationType(Entity entity)
{
Chunk chunk = null;
// Cauldron start - account for entities that dont extend EntityMob, EntityAmbientCreature, EntityCreature
if ( entity instanceof EntityMob || entity instanceof EntitySlime || entity.isCreatureType(EnumCreatureType.monster, false)) // Cauldron - account for entities that dont extend EntityMob
{
return 1; // Monster
} else if ( entity instanceof EntityCreature || entity instanceof EntityAmbientCreature || entity.isCreatureType(EnumCreatureType.creature, false)
|| entity.isCreatureType(EnumCreatureType.waterCreature, false) || entity.isCreatureType(EnumCreatureType.ambient, false))
{
return 2; // Animal
// Cauldron end
} else
{
return 3; // Misc
}
}
/**
* These entities are excluded from Activation range checks.
*
* @param entity
* @param world
* @return boolean If it should always tick.
*/
public static boolean initializeEntityActivationState(Entity entity, SpigotWorldConfig config)
{
// Cauldron start - another fix for Proxy Worlds
if (config == null && DimensionManager.getWorld(0) != null)
{
config = DimensionManager.getWorld(0).spigotConfig;
}
else
{
return true;
}
// Cauldron end
if ( ( entity.activationType == 3 && config.miscActivationRange == 0 )
|| ( entity.activationType == 2 && config.animalActivationRange == 0 )
|| ( entity.activationType == 1 && config.monsterActivationRange == 0 )
|| (entity instanceof EntityPlayer && !(entity instanceof FakePlayer)) // Cauldron
|| entity instanceof EntityThrowable
|| entity instanceof EntityDragon
|| entity instanceof EntityDragonPart
|| entity instanceof EntityWither
|| entity instanceof EntityFireball
|| entity instanceof EntityWeatherEffect
|| entity instanceof EntityTNTPrimed
|| entity instanceof EntityEnderCrystal
|| entity instanceof EntityFireworkRocket
|| entity instanceof EntityVillager
// Cauldron start - force ticks for entities with superclass of Entity and not a creature/monster
|| (entity.getClass().getSuperclass() == Entity.class && !entity.isCreatureType(EnumCreatureType.creature, false)
&& !entity.isCreatureType(EnumCreatureType.ambient, false) && !entity.isCreatureType(EnumCreatureType.monster, false)
&& !entity.isCreatureType(EnumCreatureType.waterCreature, false)))
{
return true;
}
return false;
}
/**
* Utility method to grow an AABB without creating a new AABB or touching
* the pool, so we can re-use ones we have.
*
* @param target
* @param source
* @param x
* @param y
* @param z
*/
public static void growBB(AxisAlignedBB target, AxisAlignedBB source, int x, int y, int z)
{
target.minX = source.minX - x;
target.minY = source.minY - y;
target.minZ = source.minZ - z;
target.maxX = source.maxX + x;
target.maxY = source.maxY + y;
target.maxZ = source.maxZ + z;
}
/**
* Find what entities are in range of the players in the world and set
* active if in range.
*
* @param world
*/
public static void activateEntities(World world)
{
SpigotTimings.entityActivationCheckTimer.startTiming();
// Cauldron start - proxy world support
final int miscActivationRange = world.getSpigotConfig().miscActivationRange;
final int animalActivationRange = world.getSpigotConfig().animalActivationRange;
final int monsterActivationRange = world.getSpigotConfig().monsterActivationRange;
// Cauldron end
int maxRange = Math.max( monsterActivationRange, animalActivationRange );
maxRange = Math.max( maxRange, miscActivationRange );
maxRange = Math.min( ( world.getSpigotConfig().viewDistance << 4 ) - 8, maxRange ); // Cauldron
for ( Entity player : new ArrayList<Entity>( world.playerEntities ) )
{
player.activatedTick = MinecraftServer.currentTick;
growBB( maxBB, player.boundingBox, maxRange, 256, maxRange );
growBB( miscBB, player.boundingBox, miscActivationRange, 256, miscActivationRange );
growBB( animalBB, player.boundingBox, animalActivationRange, 256, animalActivationRange );
growBB( monsterBB, player.boundingBox, monsterActivationRange, 256, monsterActivationRange );
int i = MathHelper.floor_double( maxBB.minX / 16.0D );
int j = MathHelper.floor_double( maxBB.maxX / 16.0D );
int k = MathHelper.floor_double( maxBB.minZ / 16.0D );
int l = MathHelper.floor_double( maxBB.maxZ / 16.0D );
for ( int i1 = i; i1 <= j; ++i1 )
{
for ( int j1 = k; j1 <= l; ++j1 )
{
if ( world.getWorld().isChunkLoaded( i1, j1 ) )
{
activateChunkEntities( world.getChunkFromChunkCoords( i1, j1 ) );
}
}
}
}
SpigotTimings.entityActivationCheckTimer.stopTiming();
}
/**
* Checks for the activation state of all entities in this chunk.
*
* @param chunk
*/
private static void activateChunkEntities(Chunk chunk)
{
for ( List<Entity> slice : chunk.entityLists )
{
for ( Entity entity : slice )
{
if ( MinecraftServer.currentTick > entity.activatedTick )
{
if ( entity.defaultActivationState )
{
entity.activatedTick = MinecraftServer.currentTick;
continue;
}
switch ( entity.activationType )
{
case 1:
if ( monsterBB.intersectsWith( entity.boundingBox ) )
{
entity.activatedTick = MinecraftServer.currentTick;
}
break;
case 2:
if ( animalBB.intersectsWith( entity.boundingBox ) )
{
entity.activatedTick = MinecraftServer.currentTick;
}
break;
case 3:
default:
if ( miscBB.intersectsWith( entity.boundingBox ) )
{
entity.activatedTick = MinecraftServer.currentTick;
}
}
}
}
}
}
/**
* If an entity is not in range, do some more checks to see if we should
* give it a shot.
*
* @param entity
* @return
*/
public static boolean checkEntityImmunities(Entity entity)
{
// quick checks.
if ( entity.inWater /* isInWater */ || entity.fire > 0 )
{
return true;
}
if ( !( entity instanceof EntityArrow ) )
{
if ( !entity.onGround || entity.riddenByEntity != null
|| entity.ridingEntity != null )
{
return true;
}
} else if ( !( (EntityArrow) entity ).inGround )
{
return true;
}
// special cases.
if ( entity instanceof EntityLiving )
{
EntityLiving living = (EntityLiving) entity;
if ( living.attackTime > 0 || living.hurtTime > 0 || living.activePotionsMap.size() > 0 )
{
return true;
}
if ( entity instanceof EntityCreature && ( (EntityCreature) entity ).entityToAttack != null )
{
return true;
}
if ( entity instanceof EntityVillager && ( (EntityVillager) entity ).isMating() /* Getter for first boolean */ )
{
return true;
}
if ( entity instanceof EntityAnimal )
{
EntityAnimal animal = (EntityAnimal) entity;
if ( animal.isChild() || animal.isInLove() /*love*/ )
{
return true;
}
if ( entity instanceof EntitySheep && ( (EntitySheep) entity ).getSheared() )
{
return true;
}
}
}
return false;
}
/**
* Checks if the entity is active for this tick.
*
* @param entity
* @return
*/
public static boolean checkIfActive(Entity entity)
{
SpigotTimings.checkIfActiveTimer.startTiming();
boolean isActive = entity.activatedTick >= MinecraftServer.currentTick || entity.defaultActivationState;
// Should this entity tick?
if ( !isActive )
{
if ( ( MinecraftServer.currentTick - entity.activatedTick - 1 ) % 20 == 0 )
{
// Check immunities every 20 ticks.
if ( checkEntityImmunities( entity ) )
{
// Triggered some sort of immunity, give 20 full ticks before we check again.
entity.activatedTick = MinecraftServer.currentTick + 20;
}
isActive = true;
}
// Add a little performance juice to active entities. Skip 1/4 if not immune.
} else if ( !entity.defaultActivationState && entity.ticksExisted % 4 == 0 && !checkEntityImmunities( entity ) )
{
isActive = false;
}
// Cauldron - we check for entities in forced chunks in World.updateEntityWithOptionalForce
// Make sure not on edge of unloaded chunk
int x = net.minecraft.util.MathHelper.floor_double( entity.posX );
int z = net.minecraft.util.MathHelper.floor_double( entity.posZ );
if ( isActive && !entity.worldObj.doChunksNearChunkExist( x, 0, z, 16 ) ) {
isActive = false;
}
SpigotTimings.checkIfActiveTimer.stopTiming();
return isActive;
}
}

View File

@ -0,0 +1,186 @@
package org.spigotmc;
import gnu.trove.set.TByteSet;
import gnu.trove.set.hash.TByteHashSet;
public class AntiXray
{
private static final CustomTimingsHandler update = new CustomTimingsHandler( "xray - update" );
private static final CustomTimingsHandler obfuscate = new CustomTimingsHandler( "xray - obfuscate" );
/*========================================================================*/
// Used to keep track of which blocks to obfuscate
private final boolean[] obfuscateBlocks = new boolean[ Short.MAX_VALUE ];
// Used to select a random replacement ore
private final byte[] replacementOres;
public AntiXray(SpigotWorldConfig config)
{
// Set all listed blocks as true to be obfuscated
for ( int id : ( config.engineMode == 1 ) ? config.hiddenBlocks : config.replaceBlocks )
{
obfuscateBlocks[id] = true;
}
// For every block
TByteSet blocks = new TByteHashSet();
for ( Integer i : config.hiddenBlocks )
{
net.minecraft.block.Block block = net.minecraft.block.Block.getBlockById( i );
// Check it exists and is not a tile entity
if ( block != null && !block.hasTileEntity() )
{
// Add it to the set of replacement blocks
blocks.add( (byte) (int) i );
}
}
// Bake it to a flat array of replacements
replacementOres = blocks.toArray();
}
/**
* Starts the timings handler, then updates all blocks within the set radius
* of the given coordinate, revealing them if they are hidden ores.
*/
public void updateNearbyBlocks(net.minecraft.world.World world, int x, int y, int z)
{
if ( world.getSpigotConfig().antiXray ) // Cauldron
{
update.startTiming();
updateNearbyBlocks( world, x, y, z, 2, false ); // 2 is the radius, we shouldn't change it as that would make it exponentially slower
update.stopTiming();
}
}
/**
* Removes all non exposed ores from the chunk buffer.
*/
public void obfuscate(int chunkX, int chunkZ, int bitmask, byte[] buffer, net.minecraft.world.World world)
{
// If the world is marked as obfuscated
if ( world.getSpigotConfig().antiXray ) // Cauldron
{
obfuscate.startTiming();
// Initial radius to search around for air
int initialRadius = 1;
// Which block in the buffer we are looking at, anywhere from 0 to 16^4
int index = 0;
// The iterator marking which random ore we should use next
int randomOre = 0;
// Chunk corner X and Z blocks
int startX = chunkX << 4;
int startZ = chunkZ << 4;
// Chunks can have up to 16 sections
for ( int i = 0; i < 16; i++ )
{
// If the bitmask indicates this chunk is sent...
if ( ( bitmask & 1 << i ) != 0 )
{
// Work through all blocks in the chunk, y,z,x
for ( int y = 0; y < 16; y++ )
{
for ( int z = 0; z < 16; z++ )
{
for ( int x = 0; x < 16; x++ )
{
// For some reason we can get too far ahead of ourselves (concurrent modification on bulk chunks?) so if we do, just abort and move on
if ( index >= buffer.length )
{
continue;
}
// Grab the block ID in the buffer.
// TODO: extended IDs are not yet supported
int blockId = buffer[index] & 0xFF;
// Check if the block should be obfuscated
if ( obfuscateBlocks[blockId] )
{
// TODO: Don't really understand this, but if radius is not 0 and the world isn't loaded, bail out
if ( initialRadius != 0 && !isLoaded( world, startX + x, ( i << 4 ) + y, startZ + z, initialRadius ) )
{
continue;
}
// On the otherhand, if radius is 0, or the nearby blocks are all non air, we can obfuscate
if ( initialRadius == 0 || !hasTransparentBlockAdjacent( world, startX + x, ( i << 4 ) + y, startZ + z, initialRadius ) )
{
switch ( world.spigotConfig.engineMode )
{
case 1:
// Replace with stone
buffer[index] = 1;
break;
case 2:
// Replace with random ore.
if ( randomOre >= replacementOres.length )
{
randomOre = 0;
}
buffer[index] = replacementOres[randomOre++];
break;
}
}
}
index++;
}
}
}
}
}
obfuscate.stopTiming();
}
}
private void updateNearbyBlocks(net.minecraft.world.World world, int x, int y, int z, int radius, boolean updateSelf)
{
// If the block in question is loaded
if ( world.blockExists( x, y, z ) )
{
// Get block id
net.minecraft.block.Block block = world.getBlock( x, y, z );
// See if it needs update
if ( updateSelf && obfuscateBlocks[net.minecraft.block.Block.getIdFromBlock( block )] )
{
// Send the update
world.markBlockForUpdate( x, y, z );
}
// Check other blocks for updates
if ( radius > 0 )
{
updateNearbyBlocks( world, x + 1, y, z, radius - 1, true );
updateNearbyBlocks( world, x - 1, y, z, radius - 1, true );
updateNearbyBlocks( world, x, y + 1, z, radius - 1, true );
updateNearbyBlocks( world, x, y - 1, z, radius - 1, true );
updateNearbyBlocks( world, x, y, z + 1, radius - 1, true );
updateNearbyBlocks( world, x, y, z - 1, radius - 1, true );
}
}
}
private static boolean isLoaded(net.minecraft.world.World world, int x, int y, int z, int radius)
{
return world.blockExists( x, y, z )
|| ( radius > 0
&& ( isLoaded( world, x + 1, y, z, radius - 1 )
|| isLoaded( world, x - 1, y, z, radius - 1 )
|| isLoaded( world, x, y + 1, z, radius - 1 )
|| isLoaded( world, x, y - 1, z, radius - 1 )
|| isLoaded( world, x, y, z + 1, radius - 1 )
|| isLoaded( world, x, y, z - 1, radius - 1 ) ) );
}
private static boolean hasTransparentBlockAdjacent(net.minecraft.world.World world, int x, int y, int z, int radius)
{
return !world.getBlock( x, y, z ).isNormalCube() /* isSolidBlock */
|| ( radius > 0
&& ( hasTransparentBlockAdjacent( world, x + 1, y, z, radius - 1 )
|| hasTransparentBlockAdjacent( world, x - 1, y, z, radius - 1 )
|| hasTransparentBlockAdjacent( world, x, y + 1, z, radius - 1 )
|| hasTransparentBlockAdjacent( world, x, y - 1, z, radius - 1 )
|| hasTransparentBlockAdjacent( world, x, y, z + 1, radius - 1 )
|| hasTransparentBlockAdjacent( world, x, y, z - 1, radius - 1 ) ) );
}
}

View File

@ -0,0 +1,165 @@
package org.spigotmc;
import org.bukkit.command.defaults.TimingsCommand;
import org.bukkit.event.HandlerList;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredListener;
import org.bukkit.plugin.TimedRegisteredListener;
import java.io.PrintStream;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.bukkit.Bukkit;
import org.bukkit.World;
/**
* Provides custom timing sections for /timings merged.
*/
public class CustomTimingsHandler
{
private static Queue<CustomTimingsHandler> HANDLERS = new ConcurrentLinkedQueue<CustomTimingsHandler>();
/*========================================================================*/
private final String name;
private final CustomTimingsHandler parent;
private long count = 0;
private long start = 0;
private long timingDepth = 0;
private long totalTime = 0;
private long curTickTotal = 0;
private long violations = 0;
public CustomTimingsHandler(String name)
{
this( name, null );
}
public CustomTimingsHandler(String name, CustomTimingsHandler parent)
{
this.name = name;
this.parent = parent;
HANDLERS.add( this );
}
/**
* Prints the timings and extra data to the given stream.
*
* @param printStream
*/
public static void printTimings(PrintStream printStream)
{
printStream.println( "Minecraft" );
for ( CustomTimingsHandler timings : HANDLERS )
{
long time = timings.totalTime;
long count = timings.count;
if ( count == 0 )
{
continue;
}
long avg = time / count;
printStream.println( " " + timings.name + " Time: " + time + " Count: " + count + " Avg: " + avg + " Violations: " + timings.violations );
}
printStream.println( "# Version " + Bukkit.getVersion() );
int entities = 0;
int livingEntities = 0;
for ( World world : Bukkit.getWorlds() )
{
entities += world.getEntities().size();
livingEntities += world.getLivingEntities().size();
}
printStream.println( "# Entities " + entities );
printStream.println( "# LivingEntities " + livingEntities );
}
/**
* Resets all timings.
*/
public static void reload()
{
if ( Bukkit.getPluginManager().useTimings() )
{
for ( CustomTimingsHandler timings : HANDLERS )
{
timings.reset();
}
}
TimingsCommand.timingStart = System.nanoTime();
}
/**
* Ticked every tick by CraftBukkit to count the number of times a timer
* caused TPS loss.
*/
public static void tick()
{
if ( Bukkit.getPluginManager().useTimings() )
{
for ( CustomTimingsHandler timings : HANDLERS )
{
if ( timings.curTickTotal > 50000000 )
{
timings.violations += Math.ceil( timings.curTickTotal / 50000000 );
}
timings.curTickTotal = 0;
timings.timingDepth = 0; // incase reset messes this up
}
}
}
/**
* Starts timing to track a section of code.
*/
public void startTiming()
{
// If second condtion fails we are already timing
if ( Bukkit.getPluginManager().useTimings() && ++timingDepth == 1 )
{
start = System.nanoTime();
if ( parent != null && ++parent.timingDepth == 1 )
{
parent.start = start;
}
}
}
/**
* Stops timing a section of code.
*/
public void stopTiming()
{
if ( Bukkit.getPluginManager().useTimings() )
{
if ( --timingDepth != 0 || start == 0 )
{
return;
}
long diff = System.nanoTime() - start;
totalTime += diff;
curTickTotal += diff;
count++;
start = 0;
if ( parent != null )
{
parent.stopTiming();
}
}
}
/**
* Reset this timer, setting all values to zero.
*/
public void reset()
{
count = 0;
violations = 0;
curTickTotal = 0;
totalTime = 0;
start = 0;
timingDepth = 0;
}
}

View File

@ -0,0 +1,64 @@
package org.spigotmc;
import org.bukkit.craftbukkit.util.LongHash;
public class FlatMap<V>
{
private static final int FLAT_LOOKUP_SIZE = 512;
private final Object[][] flatLookup = new Object[ FLAT_LOOKUP_SIZE * 2 ][ FLAT_LOOKUP_SIZE * 2 ];
public void put(long msw, long lsw, V value)
{
long acx = Math.abs( msw );
long acz = Math.abs( lsw );
if ( acx < FLAT_LOOKUP_SIZE && acz < FLAT_LOOKUP_SIZE )
{
flatLookup[(int) ( msw + FLAT_LOOKUP_SIZE )][(int) ( lsw + FLAT_LOOKUP_SIZE )] = value;
}
}
public void put(long key, V value)
{
put( LongHash.msw( key ), LongHash.lsw( key ), value );
}
public void remove(long key)
{
put( key, null );
}
public void remove(long msw, long lsw)
{
put( msw, lsw, null );
}
public boolean contains(long msw, long lsw)
{
return get( msw, lsw ) != null;
}
public boolean contains(long key)
{
return get( key ) != null;
}
public V get(long msw, long lsw)
{
long acx = Math.abs( msw );
long acz = Math.abs( lsw );
if ( acx < FLAT_LOOKUP_SIZE && acz < FLAT_LOOKUP_SIZE )
{
return (V) flatLookup[(int) ( msw + FLAT_LOOKUP_SIZE )][(int) ( lsw + FLAT_LOOKUP_SIZE )];
} else
{
return null;
}
}
public V get(long key)
{
return get( LongHash.msw( key ), LongHash.lsw( key ) );
}
}

View File

@ -0,0 +1,644 @@
/*
* 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 org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.scheduler.BukkitTask;
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;
/**
* <p> The metrics class obtains data about a plugin and submits statistics about it to the metrics backend. </p> <p>
* Public methods provided by this class: </p>
* <code>
* Graph createGraph(String name); <br/>
* void addCustomData(BukkitMetrics.Plotter plotter); <br/>
* void start(); <br/>
* </code>
*/
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<Graph> graphs = Collections.synchronizedSet(new HashSet<Graph>());
/**
* 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 ex) {
if (debug) {
Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage());
}
return true;
} catch (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) net.minecraft.server.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 = "Spigot";
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) {
final Iterator<Graph> iter = graphs.iterator();
while (iter.hasNext()) {
final Graph graph = iter.next();
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) {
final Iterator<Graph> iter = graphs.iterator();
while (iter.hasNext()) {
final Graph graph = iter.next();
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;
}
}
/**
* <p>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:</p>
* <code>
* StringBuffer data = new StringBuffer();
* data.append(encode("guid")).append('=').append(encode(guid));
* encodeDataPair(data, "version", description.getVersion());
* </code>
*
* @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<Plotter> plotters = new LinkedHashSet<Plotter>();
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 <b>unmodifiable</b> set of the plotter objects in the graph
*
* @return an unmodifiable {@link java.util.Set} of the plotter objects
*/
public Set<Plotter> 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();
}
}
}

View File

@ -0,0 +1,109 @@
package org.spigotmc;
import java.io.File;
import java.util.List;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.craftbukkit.util.CraftChatMessage;
public class RestartCommand extends Command
{
public RestartCommand(String name)
{
super( name );
this.description = "Restarts the server";
this.usageMessage = "/restart";
this.setPermission( "bukkit.command.restart" );
}
@Override
public boolean execute(CommandSender sender, String currentAlias, String[] args)
{
if ( testPermission( sender ) )
{
restart();
}
return true;
}
public static void restart()
{
try
{
final File file = new File( SpigotConfig.restartScript );
if ( file.isFile() )
{
System.out.println( "Attempting to restart with " + SpigotConfig.restartScript );
// Kick all players
for ( net.minecraft.entity.player.EntityPlayerMP p : (List< net.minecraft.entity.player.EntityPlayerMP>) net.minecraft.server.MinecraftServer.getServer().getConfigurationManager().playerEntityList )
{
p.playerNetServerHandler.kickPlayerFromServer(SpigotConfig.restartMessage);
p.playerNetServerHandler.netManager.isChannelOpen();
}
// Give the socket a chance to send the packets
try
{
Thread.sleep( 100 );
} catch ( InterruptedException ex )
{
}
// Close the socket so we can rebind with the new process
net.minecraft.server.MinecraftServer.getServer().func_147137_ag().terminateEndpoints();
// Give time for it to kick in
try
{
Thread.sleep( 100 );
} catch ( InterruptedException ex )
{
}
// Actually shutdown
try
{
net.minecraft.server.MinecraftServer.getServer().stopServer();
} catch ( Throwable t )
{
}
// This will be done AFTER the server has completely halted
Thread shutdownHook = new Thread()
{
@Override
public void run()
{
try
{
String os = System.getProperty( "os.name" ).toLowerCase();
if ( os.contains( "win" ) )
{
Runtime.getRuntime().exec( "cmd /c start " + file.getPath() );
} else
{
Runtime.getRuntime().exec( new String[]
{
"sh", file.getPath()
} );
}
} catch ( Exception e )
{
e.printStackTrace();
}
}
};
shutdownHook.setDaemon( true );
Runtime.getRuntime().addShutdownHook( shutdownHook );
} else
{
System.out.println( "Startup script '" + SpigotConfig.restartScript + "' does not exist! Stopping server." );
}
System.exit( 0 );
} catch ( Exception ex )
{
ex.printStackTrace();
}
}
}

View File

@ -0,0 +1,270 @@
package org.spigotmc;
import com.google.common.base.Throwables;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import gnu.trove.map.hash.TObjectIntHashMap;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.SimpleCommandMap;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
public class SpigotConfig
{
private static final File CONFIG_FILE = new File( "spigot.yml" );
private static final String HEADER = "This is the main configuration file for Spigot.\n"
+ "As you can see, there's tons to configure. Some options may impact gameplay, so use\n"
+ "with caution, and make sure you know what each option does before configuring.\n"
+ "For a reference for any variable inside this file, check out the Spigot wiki at\n"
+ "http://www.spigotmc.org/wiki/spigot-configuration/\n"
+ "\n"
+ "If you need help with the configuration or have any questions related to Spigot,\n"
+ "join us at the IRC or drop by our forums and leave a post.\n"
+ "\n"
+ "IRC: #spigot @ irc.esper.net ( http://webchat.esper.net/?channel=spigot )\n"
+ "Forums: http://www.spigotmc.org/forum/\n";
/*========================================================================*/
static YamlConfiguration config;
static int version;
static Map<String, Command> commands;
/*========================================================================*/
private static Metrics metrics;
public static void init()
{
config = YamlConfiguration.loadConfiguration( CONFIG_FILE );
config.options().header( HEADER );
config.options().copyDefaults( true );
commands = new HashMap<String, Command>();
version = getInt( "config-version", 5 );
set( "config-version", 5 );
readConfig( SpigotConfig.class, null );
}
public static void registerCommands()
{
for ( Map.Entry<String, Command> entry : commands.entrySet() )
{
net.minecraft.server.MinecraftServer.getServer().server.getCommandMap().register( entry.getKey(), "Spigot", entry.getValue() );
}
if ( metrics == null )
{
try
{
metrics = new Metrics();
metrics.start();
} catch ( IOException ex )
{
Bukkit.getServer().getLogger().log( Level.SEVERE, "Could not start metrics service", ex );
}
}
}
static void readConfig(Class<?> clazz, Object instance)
{
for ( Method method : clazz.getDeclaredMethods() )
{
if ( Modifier.isPrivate( method.getModifiers() ) )
{
if ( method.getParameterTypes().length == 0 && method.getReturnType() == Void.TYPE )
{
try
{
method.setAccessible( true );
method.invoke( instance );
} catch ( InvocationTargetException ex )
{
Throwables.propagate( ex.getCause() );
} catch ( Exception ex )
{
Bukkit.getLogger().log( Level.SEVERE, "Error invoking " + method, ex );
}
}
}
}
try
{
config.save( CONFIG_FILE );
} catch ( IOException ex )
{
Bukkit.getLogger().log( Level.SEVERE, "Could not save " + CONFIG_FILE, ex );
}
}
private static void set(String path, Object val)
{
config.set( path, val );
}
private static boolean getBoolean(String path, boolean def)
{
config.addDefault( path, def );
return config.getBoolean( path, config.getBoolean( path ) );
}
private static int getInt(String path, int def)
{
config.addDefault( path, def );
return config.getInt( path, config.getInt( path ) );
}
private static <T> List getList(String path, T def)
{
config.addDefault( path, def );
return (List<T>) config.getList( path, config.getList( path ) );
}
private static String getString(String path, String def)
{
config.addDefault( path, def );
return config.getString( path, config.getString( path ) );
}
public static boolean logCommands;
private static void logCommands()
{
logCommands = getBoolean( "commands.log", true );
}
public static boolean tabComplete;
private static void tabComplete()
{
tabComplete = getBoolean( "commands.tab-complete", true );
}
public static String whitelistMessage;
public static String unknownCommandMessage;
public static String serverFullMessage;
public static String outdatedClientMessage = "Outdated client! Please use {}";
public static String outdatedServerMessage = "Outdated server! I\'m still on {0}";
private static String transform(String s)
{
return ChatColor.translateAlternateColorCodes( '&', s ).replaceAll( "\\n", "\n" );
}
private static void messages()
{
if (version < 4)
{
set( "messages.outdated-client", outdatedClientMessage );
set( "messages.outdated-server", outdatedServerMessage );
}
whitelistMessage = transform( getString( "messages.whitelist", "You are not whitelisted on this server!" ) );
unknownCommandMessage = transform( getString( "messages.unknown-command", "Unknown command. Type \"/help\" for help." ) );
serverFullMessage = transform( getString( "messages.server-full", "The server is full!" ) );
outdatedClientMessage = transform( getString( "messages.outdated-client", outdatedClientMessage ) );
outdatedServerMessage = transform( getString( "messages.outdated-server", outdatedServerMessage ) );
}
public static int timeoutTime = 90; // Cauldron - raise to 90
public static boolean restartOnCrash = true;
public static String restartScript = "./start.sh";
public static String restartMessage;
private static void watchdog()
{
timeoutTime = getInt( "settings.timeout-time", timeoutTime );
restartOnCrash = getBoolean( "settings.restart-on-crash", restartOnCrash );
restartScript = getString( "settings.restart-script", restartScript );
restartMessage = transform( getString( "messages.restart", "Server is restarting" ) );
commands.put( "restart", new RestartCommand( "restart" ) );
WatchdogThread.doStart( timeoutTime, restartOnCrash );
}
public static boolean bungee;
private static void bungee()
{
if ( version < 4 )
{
set( "settings.bungeecord", false );
System.out.println( "Oudated config, disabling BungeeCord support!" );
}
bungee = getBoolean( "settings.bungeecord", false );
}
private static void nettyThreads()
{
int count = getInt( "settings.netty-threads", 4 );
System.setProperty( "io.netty.eventLoopThreads", Integer.toString( count ) );
Bukkit.getLogger().log( Level.INFO, "Using {0} threads for Netty based IO", count );
}
/* Cauldron - temp disable
private static void replaceCommands()
{
if ( config.contains( "replace-commands" ) ) {
set( "commands.replace-commands", config.getStringList( "replace-commands" ) );
config.set( "replace-commands", null );
}
for ( String command : (List<String>) getList( "commands.replace-commands", Arrays.asList( "setblock", "summon", "testforblock", "tellraw" ) ) )
{
SimpleCommandMap.removeFallback( command );
VanillaCommandWrapper.allowedCommands.add( command );
}
}*/
public static boolean lateBind;
private static void lateBind() {
lateBind = getBoolean( "settings.late-bind", false );
}
public static boolean disableStatSaving;
public static TObjectIntHashMap<String> forcedStats = new TObjectIntHashMap<String>();
private static void stats()
{
disableStatSaving = getBoolean( "stats.disable-saving", false );
if ( !config.contains( "stats.forced-stats" ) ) {
config.createSection( "stats.forced-stats" );
}
ConfigurationSection section = config.getConfigurationSection( "stats.forced-stats" );
for ( String name : section.getKeys( true ) )
{
if ( section.isInt( name ) )
{
forcedStats.put( name, section.getInt( name ) );
}
}
if ( disableStatSaving && section.getInt( "achievement.openInventory", 0 ) < 1 )
{
Bukkit.getLogger().warning( "*** WARNING *** stats.disable-saving is true but stats.forced-stats.achievement.openInventory" +
" isn't set to 1. Disabling stat saving without forcing the achievement may cause it to get stuck on the player's " +
"screen." );
}
}
private static void tpsCommand()
{
commands.put( "tps", new TicksPerSecondCommand( "tps" ) );
}
public static int playerSample;
private static void playerSample()
{
playerSample = getInt( "settings.sample-count", 12 );
System.out.println( "Server Ping Player Sample Count: " + playerSample );
}
public static int playerShuffle;
private static void playerShuffle()
{
playerShuffle = getInt( "settings.player-shuffle", 0 );
}
}

View File

@ -0,0 +1,281 @@
package org.spigotmc;
import java.util.Arrays;
import java.util.List;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
public class SpigotWorldConfig
{
private final String worldName;
private final YamlConfiguration config;
private boolean verbose;
public SpigotWorldConfig(String worldName)
{
this.worldName = worldName;
this.config = SpigotConfig.config;
init();
}
public void init()
{
this.verbose = getBoolean( "verbose", false ); // Cauldron - default false
log( "-------- World Settings For [" + worldName + "] --------" );
SpigotConfig.readConfig( SpigotWorldConfig.class, this );
}
private void log(String s)
{
if ( verbose )
{
Bukkit.getLogger().info( s );
}
}
private void set(String path, Object val)
{
config.set( "world-settings.default." + path, val );
}
private boolean getBoolean(String path, boolean def)
{
config.addDefault( "world-settings.default." + path, def );
return config.getBoolean( "world-settings." + worldName + "." + path, config.getBoolean( "world-settings.default." + path ) );
}
private double getDouble(String path, double def)
{
config.addDefault( "world-settings.default." + path, def );
return config.getDouble( "world-settings." + worldName + "." + path, config.getDouble( "world-settings.default." + path ) );
}
private int getInt(String path, int def)
{
config.addDefault( "world-settings.default." + path, def );
return config.getInt( "world-settings." + worldName + "." + path, config.getInt( "world-settings.default." + path ) );
}
private <T> List getList(String path, T def)
{
config.addDefault( "world-settings.default." + path, def );
return (List<T>) config.getList( "world-settings." + worldName + "." + path, config.getList( "world-settings.default." + path ) );
}
private String getString(String path, String def)
{
config.addDefault( "world-settings.default." + path, def );
return config.getString( "world-settings." + worldName + "." + path, config.getString( "world-settings.default." + path ) );
}
public int chunksPerTick;
public boolean clearChunksOnTick;
private void chunksPerTick()
{
chunksPerTick = getInt( "chunks-per-tick", 650 );
log( "Chunks to Grow per Tick: " + chunksPerTick );
clearChunksOnTick = getBoolean( "clear-tick-list", false );
log( "Clear tick list: " + false );
}
// Crop growth rates
public int cactusModifier;
public int caneModifier;
public int melonModifier;
public int mushroomModifier;
public int pumpkinModifier;
public int saplingModifier;
public int wheatModifier;
private int getAndValidateGrowth(String crop)
{
int modifier = getInt( "growth." + crop.toLowerCase() + "-modifier", 100 );
if ( modifier == 0 )
{
log( "Cannot set " + crop + " growth to zero, defaulting to 100" );
modifier = 100;
}
log( crop + " Growth Modifier: " + modifier + "%" );
return modifier;
}
private void growthModifiers()
{
cactusModifier = getAndValidateGrowth( "Cactus" );
caneModifier = getAndValidateGrowth( "Cane" );
melonModifier = getAndValidateGrowth( "Melon" );
mushroomModifier = getAndValidateGrowth( "Mushroom" );
pumpkinModifier = getAndValidateGrowth( "Pumpkin" );
saplingModifier = getAndValidateGrowth( "Sapling" );
wheatModifier = getAndValidateGrowth( "Wheat" );
}
public double itemMerge;
private void itemMerge()
{
itemMerge = getDouble("merge-radius.item", 2.5 );
log( "Item Merge Radius: " + itemMerge );
}
public double expMerge;
private void expMerge()
{
expMerge = getDouble("merge-radius.exp", 3.0 );
log( "Experience Merge Radius: " + expMerge );
}
public int viewDistance;
private void viewDistance()
{
viewDistance = getInt( "view-distance", Bukkit.getViewDistance() );
log( "View Distance: " + viewDistance );
}
public byte mobSpawnRange;
private void mobSpawnRange()
{
mobSpawnRange = (byte) getInt( "mob-spawn-range", 4 );
log( "Mob Spawn Range: " + mobSpawnRange );
}
public int animalActivationRange = 32;
public int monsterActivationRange = 32;
public int miscActivationRange = 16;
private void activationRange()
{
animalActivationRange = getInt( "entity-activation-range.animals", animalActivationRange );
monsterActivationRange = getInt( "entity-activation-range.monsters", monsterActivationRange );
miscActivationRange = getInt( "entity-activation-range.misc", miscActivationRange );
log( "Entity Activation Range: An " + animalActivationRange + " / Mo " + monsterActivationRange + " / Mi " + miscActivationRange );
}
public int playerTrackingRange = 48;
public int animalTrackingRange = 48;
public int monsterTrackingRange = 48;
public int miscTrackingRange = 32;
public int maxTrackingRange = 64;
private void trackingRange()
{
playerTrackingRange = getInt( "entity-tracking-range.players", playerTrackingRange );
animalTrackingRange = getInt( "entity-tracking-range.animals", animalTrackingRange );
monsterTrackingRange = getInt( "entity-tracking-range.monsters", monsterTrackingRange );
miscTrackingRange = getInt( "entity-tracking-range.misc", miscTrackingRange );
maxTrackingRange = getInt( "entity-tracking-range.other", maxTrackingRange );
log( "Entity Tracking Range: Pl " + playerTrackingRange + " / An " + animalTrackingRange + " / Mo " + monsterTrackingRange + " / Mi " + miscTrackingRange + " / Other " + maxTrackingRange );
}
public int hopperTransfer = 8;
public int hopperCheck = 8;
private void hoppers()
{
// Set the tick delay between hopper item movements
hopperTransfer = getInt( "ticks-per.hopper-transfer", hopperTransfer );
// Set the tick delay between checking for items after the associated
// container is empty. Default to the hopperTransfer value to prevent
// hopper sorting machines from becoming out of sync.
hopperCheck = getInt( "ticks-per.hopper-check", hopperTransfer );
log( "Hopper Transfer: " + hopperTransfer + " Hopper Check: " + hopperCheck );
}
public boolean randomLightUpdates;
private void lightUpdates()
{
randomLightUpdates = getBoolean( "random-light-updates", false );
log( "Random Lighting Updates: " + randomLightUpdates );
}
public boolean saveStructureInfo;
private void structureInfo()
{
saveStructureInfo = getBoolean( "save-structure-info", true );
log( "Structure Info Saving: " + saveStructureInfo );
if ( !saveStructureInfo )
{
log( "*** WARNING *** You have selected to NOT save structure info. This may cause structures such as fortresses to not spawn mobs when updating to 1.7!" );
log( "*** WARNING *** Please use this option with caution, SpigotMC is not responsible for any issues this option may cause in the future!" );
}
}
public int itemDespawnRate;
private void itemDespawnRate()
{
itemDespawnRate = getInt( "item-despawn-rate", 6000 );
log( "Item Despawn Rate: " + itemDespawnRate );
}
public int arrowDespawnRate;
private void arrowDespawnRate()
{
arrowDespawnRate = getInt( "arrow-despawn-rate", 1200 );
log( "Arrow Despawn Rate: " + arrowDespawnRate );
}
public boolean antiXray;
public int engineMode;
public List<Integer> hiddenBlocks;
public List<Integer> replaceBlocks;
public AntiXray antiXrayInstance;
private void antiXray()
{
antiXray = getBoolean( "anti-xray.enabled", true );
log( "Anti X-Ray: " + antiXray );
engineMode = getInt( "anti-xray.engine-mode", 1 );
log( "\tEngine Mode: " + engineMode );
if ( SpigotConfig.version < 5 )
{
set( "anti-xray.blocks", null );
}
hiddenBlocks = getList( "anti-xray.hide-blocks", Arrays.asList( new Integer[]
{
14, 15, 16, 21, 48, 49, 54, 56, 73, 74, 82, 129, 130
} ) );
log( "\tHidden Blocks: " + hiddenBlocks );
replaceBlocks = getList( "anti-xray.replace-blocks", Arrays.asList( new Integer[]
{
1, 5
} ) );
log( "\tReplace Blocks: " + replaceBlocks );
antiXrayInstance = new AntiXray( this );
}
public boolean zombieAggressiveTowardsVillager;
private void zombieAggressiveTowardsVillager()
{
zombieAggressiveTowardsVillager = getBoolean( "zombie-aggressive-towards-villager", true );
log( "Zombie Aggressive Towards Villager: " + zombieAggressiveTowardsVillager );
}
public boolean nerfSpawnerMobs;
private void nerfSpawnerMobs()
{
nerfSpawnerMobs = getBoolean( "nerf-spawner-mobs", false );
log( "Nerfing mobs spawned from spawners: " + nerfSpawnerMobs );
}
public boolean enableZombiePigmenPortalSpawns;
private void enableZombiePigmenPortalSpawns()
{
enableZombiePigmenPortalSpawns = getBoolean( "enable-zombie-pigmen-portal-spawns", true );
log( "Allow Zombie Pigmen to spawn from portal blocks: " + enableZombiePigmenPortalSpawns );
}
public int maxBulkChunk;
private void bulkChunkCount()
{
maxBulkChunk = getInt( "max-bulk-chunks", 5 );
log( "Sending up to " + maxBulkChunk + " chunks per packet" );
}
public int maxCollisionsPerEntity;
private void maxEntityCollision()
{
maxCollisionsPerEntity = getInt( "max-entity-collisions", 8 );
log( "Max Entity Collisions: " + maxCollisionsPerEntity );
}
}

View File

@ -0,0 +1,44 @@
package org.spigotmc;
import com.google.common.base.Joiner;
import com.google.common.collect.Iterables;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
public class TicksPerSecondCommand extends Command
{
public TicksPerSecondCommand(String name)
{
super( name );
this.description = "Gets the current ticks per second for the server";
this.usageMessage = "/tps";
this.setPermission( "bukkit.command.tps" );
}
@Override
public boolean execute(CommandSender sender, String currentAlias, String[] args)
{
if ( !testPermission( sender ) )
{
return true;
}
StringBuilder sb = new StringBuilder( ChatColor.GOLD + "TPS from last 1m, 5m, 15m: " );
for ( double tps : net.minecraft.server.MinecraftServer.getServer().recentTps )
{
sb.append( format( tps ) );
sb.append( ", " );
}
sender.sendMessage( sb.substring( 0, sb.length() - 2 ) );
return true;
}
private String format(double tps)
{
return ( ( tps > 18.0 ) ? ChatColor.GREEN : ( tps > 16.0 ) ? ChatColor.YELLOW : ChatColor.RED ).toString()
+ ( ( tps > 20.0 ) ? "*" : "" ) + Math.min( Math.round( tps * 100.0 ) / 100.0, 20.0 );
}
}

View File

@ -0,0 +1,51 @@
package org.spigotmc;
import net.minecraft.entity.Entity;
import net.minecraft.entity.item.EntityXPOrb;
import net.minecraft.entity.monster.EntityGhast;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.item.EntityItemFrame;
import net.minecraft.entity.item.EntityPainting;
import net.minecraft.entity.player.EntityPlayerMP;;
public class TrackingRange
{
/**
* Gets the range an entity should be 'tracked' by players and visible in
* the client.
*
* @param entity
* @param defaultRange Default range defined by Mojang
* @return
*/
public static int getEntityTrackingRange(Entity entity, int defaultRange)
{
SpigotWorldConfig config = entity.worldObj.getSpigotConfig(); // Cauldron
int range = defaultRange;
if ( entity instanceof EntityPlayerMP )
{
range = config.playerTrackingRange;
} else if ( entity.defaultActivationState || entity instanceof EntityGhast )
{
range = defaultRange;
} else if ( entity.activationType == 1 )
{
range = config.monsterTrackingRange;
} else if ( entity.activationType == 2 )
{
range = config.animalTrackingRange;
} else if ( entity instanceof EntityItemFrame || entity instanceof EntityPainting || entity instanceof EntityItem || entity instanceof EntityXPOrb )
{
range = config.miscTrackingRange;
}
// Cauldron start - allow for 0 to disable tracking ranges
if (range == 0)
{
return defaultRange;
}
// Cauldron end
return Math.min( config.maxTrackingRange, range );
}
}

View File

@ -0,0 +1,186 @@
package org.spigotmc;
import com.google.common.collect.ImmutableList;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.block.Block;
import org.bukkit.command.CommandSender;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.command.CraftBlockCommandSender;
import org.bukkit.craftbukkit.entity.CraftMinecartCommand;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
public class VanillaCommandWrapper
{
public static final HashSet<String> allowedCommands = new HashSet<String>();
public static int dispatch(CommandSender sender, String commandLine)
{
int pos = commandLine.indexOf( ' ' );
if ( pos == -1 )
{
pos = commandLine.length();
}
String name = commandLine.substring( 0, pos );
if ( !allowedCommands.contains( name ) )
{
return -1;
}
if ( !sender.hasPermission( "bukkit.command." + name ) )
{
sender.sendMessage( ChatColor.RED + "You do not have permission for this command" );
return 0;
}
net.minecraft.command.ICommandSender listener = getListener( sender );
if ( listener == null )
{
return -1;
}
return net.minecraft.server.MinecraftServer.getServer().getCommandManager().executeCommand( listener, commandLine );
}
public static List<String> complete(CommandSender sender, String commandLine)
{
int pos = commandLine.indexOf( ' ' );
if ( pos == -1 )
{
List<String> completions = new ArrayList<String>();
commandLine = commandLine.toLowerCase();
for ( String command : allowedCommands )
{
if ( command.startsWith( commandLine ) && sender.hasPermission( "bukkit.command." + command ) )
{
completions.add( "/" + command );
}
}
return completions;
}
String name = commandLine.substring( 0, pos );
if ( !allowedCommands.contains( name ) || !sender.hasPermission( "bukkit.command." + name ) )
{
return ImmutableList.<String>of();
}
net.minecraft.command.ICommandSender listener = getListener( sender );
if ( listener == null )
{
return ImmutableList.<String>of();
}
return net.minecraft.server.MinecraftServer.getServer().getCommandManager().getPossibleCommands( listener, commandLine );
}
private static net.minecraft.command.ICommandSender getListener(CommandSender sender)
{
if ( sender instanceof CraftPlayer )
{
return new PlayerListener( ( (CraftPlayer) sender ).getHandle() );
}
if ( sender instanceof CraftBlockCommandSender )
{
CraftBlockCommandSender commandBlock = (CraftBlockCommandSender) sender;
Block block = commandBlock.getBlock();
return ( (net.minecraft.tileentity.TileEntityCommandBlock) ( (CraftWorld) block.getWorld() ).getTileEntityAt( block.getX(), block.getY(), block.getZ() ) ).func_145993_a();
}
if ( sender instanceof CraftMinecartCommand )
{
return ( (net.minecraft.entity.EntityMinecartCommandBlock) ( (CraftMinecartCommand) sender ).getHandle() ).func_145822_e();
}
return new ConsoleListener(sender); // Assume console/rcon
}
private static class PlayerListener implements net.minecraft.command.ICommandSender
{
private final net.minecraft.command.ICommandSender handle;
public PlayerListener(net.minecraft.command.ICommandSender handle)
{
this.handle = handle;
}
@Override
public String getCommandSenderName()
{
return handle.getCommandSenderName();
}
@Override
public net.minecraft.util.IChatComponent func_145748_c_()
{
return handle.func_145748_c_();
}
@Override
public void addChatMessage(net.minecraft.util.IChatComponent iChatBaseComponent)
{
handle.addChatMessage( iChatBaseComponent );
}
@Override
public boolean canCommandSenderUseCommand(int i, String s)
{
return true;
}
@Override
public net.minecraft.util.ChunkCoordinates getPlayerCoordinates()
{
return handle.getPlayerCoordinates();
}
@Override
public net.minecraft.world.World getEntityWorld()
{
return handle.getEntityWorld();
}
}
private static class ConsoleListener implements net.minecraft.command.ICommandSender {
private final CommandSender sender;
public ConsoleListener( CommandSender sender )
{
this.sender = sender;
}
@Override
public String getCommandSenderName()
{
return sender.getName();
}
@Override
public net.minecraft.util.IChatComponent func_145748_c_()
{
return new net.minecraft.util.ChatComponentText( getCommandSenderName() );
}
@Override
public void addChatMessage( net.minecraft.util.IChatComponent iChatBaseComponent )
{
sender.sendMessage( iChatBaseComponent.getUnformattedTextForChat() );
}
@Override
public boolean canCommandSenderUseCommand( int i, String s )
{
return true;
}
@Override
public net.minecraft.util.ChunkCoordinates getPlayerCoordinates()
{
return new net.minecraft.util.ChunkCoordinates( 0, 0, 0 );
}
@Override
public net.minecraft.world.World getEntityWorld()
{
return net.minecraft.server.MinecraftServer.getServer().getEntityWorld();
}
}
}

View File

@ -0,0 +1,220 @@
package org.spigotmc;
import java.io.File;
import java.io.FileWriter;
import java.lang.management.ManagementFactory;
import java.lang.management.MonitorInfo;
import java.lang.management.ThreadInfo;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.chunk.Chunk;
import net.minecraftforge.cauldron.CauldronHooks;
import net.minecraftforge.cauldron.configuration.CauldronConfig;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
public class WatchdogThread extends Thread
{
private static WatchdogThread instance;
private final long timeoutTime;
private final long warningTime;
private final boolean restart;
private volatile long lastTick;
private volatile boolean stopping;
private volatile long lastWarning;
private WatchdogThread(long timeoutTime, boolean restart)
{
super( "Spigot Watchdog Thread" );
this.timeoutTime = timeoutTime;
this.warningTime = Math.max(timeoutTime/3, 5000);
this.restart = restart;
}
public static void doStart(int timeoutTime, boolean restart)
{
if ( instance == null )
{
instance = new WatchdogThread( timeoutTime * 1000L, restart );
instance.start();
}
}
public static void tick()
{
instance.lastTick = System.currentTimeMillis();
}
public static void doStop()
{
if ( instance != null )
{
instance.stopping = true;
}
}
@Override
public void run()
{
while (!stopping)
{
// Trigger watchdog logic
long currentTime = System.currentTimeMillis();
if ((lastTick != 0 && currentTime > lastTick + timeoutTime))
{
Logger log = Bukkit.getServer().getLogger();
log.log(Level.SEVERE, "The server has stopped responding!");
log.log(Level.SEVERE, "Please report this to https://github.com/MinecraftPortCentral/Cauldron/issues");
log.log(Level.SEVERE, "Be sure to include ALL relevant console errors and Minecraft crash reports");
log.log(Level.SEVERE, "Cauldron version: " + Bukkit.getServer().getVersion());
// Cauldron start - add more logging info
log.log(Level.SEVERE, "The server is going slow. Last server tick was " + (currentTime - lastTick) + "ms ago");
double tps = Math.min(20, Math.round(net.minecraft.server.MinecraftServer.currentTps * 10) / 10.0);
log.log(Level.SEVERE, "Last Tick: " + lastTick + " Current Time: " + currentTime + " Warning: " + warningTime + " Timeout: " + timeoutTime);
log.log(Level.SEVERE, "[TPS]: " + tps + " Server Tick #" + net.minecraft.server.MinecraftServer.getServer().getTickCounter());
log.log(Level.SEVERE, "Last recorded TPS: " + tps);
// Dump world info
log.log(Level.SEVERE, "------------------------------");
log.log(Level.SEVERE, "Loaded dimensions:");
for (net.minecraft.world.WorldServer world : MinecraftServer.getServer().worlds)
{
log.log(Level.SEVERE, " Dimension:" + world.provider.dimensionId);
log.log(Level.SEVERE,
" Loaded Chunks: " + world.theChunkProviderServer.loadedChunkHashMap.size() + " Active Chunks: " + world.activeChunkSet.size()
+ " Entities: " + world.loadedEntityList.size() + " Tile Entities: " + world.loadedTileEntityList.size());
log.log(Level.SEVERE, " Entities Last Tick: " + world.entitiesTicked);
log.log(Level.SEVERE, " Tiles Last Tick: " + world.tilesTicked);
}
log.log(Level.SEVERE, "------------------------------");
if (MinecraftServer.getServer().cauldronConfig.dumpChunksOnDeadlock.getValue())
{
// Dump detailed world info to a watchdog report log
File file = new File(new File(new File("."), "crash-reports"), "watchdog-chunks-"
+ (new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss")).format(new Date()) + "-server.txt");
log.log(Level.SEVERE, "------------------------------");
log.log(Level.SEVERE, "Writing watchdog detailed info to: " + file);
CauldronHooks.writeChunks(file, false);
log.log(Level.SEVERE, "Writing complete");
log.log(Level.SEVERE, "------------------------------");
}
if (MinecraftServer.getServer().cauldronConfig.dumpHeapOnDeadlock.getValue())
{
// Dump detailed world info to a watchdog report log
File file = new File(new File(new File("."), "crash-reports"), "watchdog-heap-"
+ (new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss")).format(new Date()) + "-server.bin");
log.log(Level.SEVERE, "------------------------------");
log.log(Level.SEVERE, "Writing heap dump to: " + file);
CauldronHooks.dumpHeap(file, true);
log.log(Level.SEVERE, "Writing complete");
log.log(Level.SEVERE, "------------------------------");
}
// Cauldron end
log.log(Level.SEVERE, "------------------------------");
log.log(Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Cauldron!):");
dumpThread(ManagementFactory.getThreadMXBean().getThreadInfo(MinecraftServer.getServer().primaryThread.getId(), Integer.MAX_VALUE), log);
log.log(Level.SEVERE, "------------------------------");
//
log.log(Level.SEVERE, "Entire Thread Dump:");
ThreadInfo[] threads = ManagementFactory.getThreadMXBean().dumpAllThreads(true, true);
for (ThreadInfo thread : threads)
{
dumpThread(thread, log);
}
log.log(Level.SEVERE, "------------------------------");
if (restart)
{
RestartCommand.restart();
}
break;
}
// Cauldron + start - add warning info
else if (lastTick != 0 && System.currentTimeMillis() > lastTick + warningTime)
{
Logger log = Bukkit.getServer().getLogger();
lastWarning = System.currentTimeMillis();
// Print what the last server TPS was...
log.log(Level.WARNING, "The server is going slow. Last server tick was " + ((System.currentTimeMillis() - lastTick)) + "ms ago");
double tps = Math.min(20, Math.round(net.minecraft.server.MinecraftServer.currentTps * 10) / 10.0);
log.log(Level.WARNING, "Last Tick: " + lastTick + " Current Time: " + currentTime + " Warning: " + warningTime + " Timeout: " + timeoutTime);
log.log(Level.WARNING, "[TPS]: " + tps + " Server Tick #" + net.minecraft.server.MinecraftServer.getServer().getTickCounter());
for (net.minecraft.world.WorldServer world : MinecraftServer.getServer().worlds)
{
log.log(Level.WARNING, " Dimension:" + world.provider.dimensionId);
log.log(Level.WARNING, " Loaded Chunks: " + world.theChunkProviderServer.loadedChunkHashMap.size() +
" Active Chunks: " + world.activeChunkSet.size() +
" Entities: " + world.loadedEntityList.size() +
" Tile Entities: " + world.loadedTileEntityList.size());
log.log(Level.WARNING, " Entities Last Tick: " + world.entitiesTicked);
log.log(Level.WARNING, " Tiles Last Tick: " + world.tilesTicked);
}
if (MinecraftServer.getServer().cauldronConfig.dumpThreadsOnWarn.getValue())
{
log.log(Level.WARNING, "Server thread dump (Look for mods or plugins here before reporting to Cauldron!):");
dumpThread(ManagementFactory.getThreadMXBean().getThreadInfo(MinecraftServer.getServer().primaryThread.getId(), Integer.MAX_VALUE), log,
Level.WARNING);
}
}
// Cauldron end
try
{
sleep(10000);
}
catch (InterruptedException ex)
{
interrupt();
}
}
}
private static void dumpThread(ThreadInfo thread, Logger log)
{
dumpThread(thread, log, Level.SEVERE);
}
private static void dumpThread(ThreadInfo thread, Logger log, Level level)
{
if (thread == null) return;
if ( thread.getThreadState() != State.WAITING )
{
log.log( level, "------------------------------" );
//
log.log( level, "Current Thread: " + thread.getThreadName() );
log.log( level, "\tPID: " + thread.getThreadId()
+ " | Suspended: " + thread.isSuspended()
+ " | Native: " + thread.isInNative()
+ " | State: " + thread.getThreadState()
+ " | Blocked Time: " + thread.getBlockedTime() // Cauldron add info about blocked time
+ " | Blocked Count: " + thread.getBlockedCount()); // Cauldron add info about blocked count
if ( thread.getLockedMonitors().length != 0 )
{
log.log( level, "\tThread is waiting on monitor(s):" );
for ( MonitorInfo monitor : thread.getLockedMonitors() )
{
log.log( level, "\t\tLocked on:" + monitor.getLockedStackFrame() );
}
}
if ( thread.getLockOwnerId() != -1 ) log.log( level, "\tLock Owner Id: " + thread.getLockOwnerId()); // Cauldron + add info about lock owner thread id
log.log( level, "\tStack:" );
//
StackTraceElement[] stack = thread.getStackTrace();
for ( int line = 0; line < stack.length; line++ )
{
log.log( level, "\t\t" + stack[line].toString() );
}
}
}
}

View File

@ -0,0 +1,39 @@
package org.spigotmc.event.entity;
import org.bukkit.entity.Entity;
import org.bukkit.event.HandlerList;
import org.bukkit.event.entity.EntityEvent;
/**
* Called when an entity stops riding another entity.
*
*/
public class EntityDismountEvent extends EntityEvent
{
private static final HandlerList handlers = new HandlerList();
private boolean cancelled;
private final Entity dismounted;
public EntityDismountEvent(Entity what, Entity dismounted)
{
super( what );
this.dismounted = dismounted;
}
public Entity getDismounted()
{
return dismounted;
}
@Override
public HandlerList getHandlers()
{
return handlers;
}
public static HandlerList getHandlerList()
{
return handlers;
}
}

View File

@ -0,0 +1,52 @@
package org.spigotmc.event.entity;
import org.bukkit.entity.Entity;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.entity.EntityEvent;
/**
* Called when an entity attempts to ride another entity.
*
*/
public class EntityMountEvent extends EntityEvent implements Cancellable
{
private static final HandlerList handlers = new HandlerList();
private boolean cancelled;
private final Entity mount;
public EntityMountEvent(Entity what, Entity mount)
{
super( what );
this.mount = mount;
}
public Entity getMount()
{
return mount;
}
@Override
public boolean isCancelled()
{
return cancelled;
}
@Override
public void setCancelled(boolean cancel)
{
this.cancelled = cancel;
}
@Override
public HandlerList getHandlers()
{
return handlers;
}
public static HandlerList getHandlerList()
{
return handlers;
}
}