Initial commit (Forge 1291).
This commit is contained in:
327
src/main/java/org/spigotmc/ActivationRange.java
Normal file
327
src/main/java/org/spigotmc/ActivationRange.java
Normal 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;
|
||||
}
|
||||
}
|
186
src/main/java/org/spigotmc/AntiXray.java
Normal file
186
src/main/java/org/spigotmc/AntiXray.java
Normal 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 ) ) );
|
||||
}
|
||||
}
|
165
src/main/java/org/spigotmc/CustomTimingsHandler.java
Normal file
165
src/main/java/org/spigotmc/CustomTimingsHandler.java
Normal 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;
|
||||
}
|
||||
}
|
64
src/main/java/org/spigotmc/FlatMap.java
Normal file
64
src/main/java/org/spigotmc/FlatMap.java
Normal 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 ) );
|
||||
}
|
||||
}
|
644
src/main/java/org/spigotmc/Metrics.java
Normal file
644
src/main/java/org/spigotmc/Metrics.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
109
src/main/java/org/spigotmc/RestartCommand.java
Normal file
109
src/main/java/org/spigotmc/RestartCommand.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
270
src/main/java/org/spigotmc/SpigotConfig.java
Normal file
270
src/main/java/org/spigotmc/SpigotConfig.java
Normal 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 );
|
||||
}
|
||||
}
|
281
src/main/java/org/spigotmc/SpigotWorldConfig.java
Normal file
281
src/main/java/org/spigotmc/SpigotWorldConfig.java
Normal 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 );
|
||||
}
|
||||
}
|
44
src/main/java/org/spigotmc/TicksPerSecondCommand.java
Normal file
44
src/main/java/org/spigotmc/TicksPerSecondCommand.java
Normal 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 );
|
||||
}
|
||||
}
|
51
src/main/java/org/spigotmc/TrackingRange.java
Normal file
51
src/main/java/org/spigotmc/TrackingRange.java
Normal 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 );
|
||||
}
|
||||
}
|
186
src/main/java/org/spigotmc/VanillaCommandWrapper.java
Normal file
186
src/main/java/org/spigotmc/VanillaCommandWrapper.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
220
src/main/java/org/spigotmc/WatchdogThread.java
Normal file
220
src/main/java/org/spigotmc/WatchdogThread.java
Normal 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() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user