blob: 6379b36fb9014ad31d3b0e55b6db299339ebebdd [file] [log] [blame] [raw]
package net.glowstone;
import lombok.ToString;
import net.glowstone.block.GlowBlock;
import net.glowstone.constants.GlowBiome;
import net.glowstone.constants.GlowEffect;
import net.glowstone.constants.GlowParticle;
import net.glowstone.entity.*;
import net.glowstone.entity.objects.GlowItem;
import net.glowstone.generator.TreeGenerator;
import net.glowstone.io.WorldMetadataService.WorldFinalValues;
import net.glowstone.io.WorldStorageProvider;
import net.glowstone.io.anvil.AnvilWorldStorageProvider;
import net.glowstone.net.message.play.entity.EntityStatusMessage;
import net.glowstone.net.message.play.player.ServerDifficultyMessage;
import net.glowstone.util.BlockStateDelegate;
import net.glowstone.util.GameRuleManager;
import org.bukkit.*;
import org.bukkit.block.Biome;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.entity.*;
import org.bukkit.event.weather.LightningStrikeEvent;
import org.bukkit.event.weather.ThunderChangeEvent;
import org.bukkit.event.weather.WeatherChangeEvent;
import org.bukkit.event.world.*;
import org.bukkit.generator.BlockPopulator;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.inventory.ItemStack;
import org.bukkit.material.MaterialData;
import org.bukkit.metadata.MetadataStore;
import org.bukkit.metadata.MetadataStoreBase;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.messaging.StandardMessenger;
import org.bukkit.util.Vector;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.logging.Level;
/**
* A class which represents the in-game world.
* @author Graham Edgecombe
*/
@ToString(of = "name")
public final class GlowWorld implements World {
/**
* The metadata store class for worlds.
*/
private static final class WorldMetadataStore extends MetadataStoreBase<World> implements MetadataStore<World> {
@Override
protected String disambiguate(World subject, String metadataKey) {
return subject.getName() + ":" + metadataKey;
}
}
/**
* The metadata store for world objects.
*/
private static final MetadataStore<World> metadata = new WorldMetadataStore();
/**
* The length in ticks of one Minecraft day.
*/
public static final long DAY_LENGTH = 24000;
/**
* The length in ticks between autosaves (5 minutes).
*/
private static final int AUTOSAVE_TIME = 20 * 60 * 5;
/**
* The server of this world.
*/
private final GlowServer server;
/**
* The name of this world.
*/
private final String name;
/**
* The chunk manager.
*/
private final ChunkManager chunks;
/**
* A lock kept on the spawn chunks.
*/
private final ChunkManager.ChunkLock spawnChunkLock;
/**
* The world metadata service used.
*/
private final WorldStorageProvider storageProvider;
/**
* The world's UUID
*/
private final UUID uid;
/**
* The entity manager.
*/
private final EntityManager entities = new EntityManager();
/**
* This world's Random instance.
*/
private final Random random = new Random();
/**
* The world populators for this world.
*/
private final List<BlockPopulator> populators;
/**
* The game rules used in this world.
*/
private final GameRuleManager gameRules = new GameRuleManager();
/**
* The environment.
*/
private final Environment environment;
/**
* The world type.
*/
private final WorldType worldType;
/**
* Whether structure generation is enabled.
*/
private final boolean generateStructures;
/**
* The world seed.
*/
private final long seed;
/**
* The spawn position.
*/
private Location spawnLocation;
/**
* Whether to keep the spawn chunks in memory (prevent them from being unloaded)
*/
private boolean keepSpawnLoaded = true;
/**
* Whether PvP is allowed in this world.
*/
private boolean pvpAllowed = true;
/**
* Whether animals can spawn in this world.
*/
private boolean spawnAnimals = true;
/**
* Whether monsters can spawn in this world.
*/
private boolean spawnMonsters = true;
/**
* Whether it is currently raining/snowing on this world.
*/
private boolean currentlyRaining = false;
/**
* How many ticks until the rain/snow status is expected to change.
*/
private int rainingTicks = 0;
/**
* Whether it is currently thundering on this world.
*/
private boolean currentlyThundering = false;
/**
* How many ticks until the thundering status is expected to change.
*/
private int thunderingTicks = 0;
/**
* The rain density on the current world tick.
*/
private float currentRainDensity = 0;
/**
* The sky darkness on the current world tick.
*/
private float currentSkyDarkness = 0;
/**
* The age of the world, in ticks.
*/
private long worldAge = 0;
/**
* The current world time.
*/
private long time = 0;
/**
* The time until the next full-save.
*/
private int saveTimer = AUTOSAVE_TIME;
/**
* The check to autosave
*/
private boolean autosave = true;
/**
* The world's gameplay difficulty.
*/
private Difficulty difficulty = Difficulty.PEACEFUL;
/**
* Ticks between when various types of entities are spawned.
*/
private long ticksPerAnimal, ticksPerMonster;
/**
* Per-chunk spawn limits on various types of entities.
*/
private int monsterLimit, animalLimit, waterAnimalLimit, ambientLimit;
/**
* Contains how regular blocks should be pulsed.
*/
private final Map tickMap = new HashMap<>();
/**
* Creates a new world from the options in the given WorldCreator.
* @param server The server for the world.
* @param creator The WorldCreator to use.
*/
public GlowWorld(GlowServer server, WorldCreator creator) {
this.server = server;
// set up values from WorldCreator
name = creator.name();
environment = creator.environment();
worldType = creator.type();
generateStructures = creator.generateStructures();
final ChunkGenerator generator = creator.generator();
storageProvider = new AnvilWorldStorageProvider(new File(server.getWorldContainer(), name));
storageProvider.setWorld(this);
chunks = new ChunkManager(this, storageProvider.getChunkIoService(), generator);
populators = generator.getDefaultPopulators(this);
// set up values from server defaults
ticksPerAnimal = server.getTicksPerAnimalSpawns();
ticksPerMonster = server.getTicksPerMonsterSpawns();
monsterLimit = server.getMonsterSpawnLimit();
animalLimit = server.getAnimalSpawnLimit();
waterAnimalLimit = server.getWaterAnimalSpawnLimit();
ambientLimit = server.getAmbientSpawnLimit();
keepSpawnLoaded = server.keepSpawnLoaded();
difficulty = server.getDifficulty();
// read in world data
WorldFinalValues values = null;
try {
values = storageProvider.getMetadataService().readWorldData();
} catch (IOException e) {
server.getLogger().log(Level.SEVERE, "Error reading world for creation", e);
}
if (values != null) {
if (values.getSeed() == 0L) {
this.seed = creator.seed();
} else {
this.seed = values.getSeed();
}
this.uid = values.getUuid();
} else {
this.seed = creator.seed();
this.uid = UUID.randomUUID();
}
// begin loading spawn area
spawnChunkLock = newChunkLock("spawn");
server.addWorld(this);
server.getLogger().info("Preparing spawn for " + name + "...");
EventFactory.callEvent(new WorldInitEvent(this));
// determine the spawn location if we need to
if (spawnLocation == null) {
// no location loaded, look for fixed spawn
spawnLocation = generator.getFixedSpawnLocation(this, random);
if (spawnLocation == null) {
// determine a location randomly
int spawnX = random.nextInt(128) - 64, spawnZ = random.nextInt(128) - 64;
GlowChunk chunk = getChunkAt(spawnX >> 4, spawnZ >> 4);
//GlowServer.logger.info("determining spawn: " + chunk.getX() + " " + chunk.getZ());
chunk.load(true); // I'm not sure there's a sane way around this
for (int tries = 0; tries < 10 && !generator.canSpawn(this, spawnX, spawnZ); ++tries) {
spawnX += random.nextInt(128) - 64;
spawnZ += random.nextInt(128) - 64;
}
setSpawnLocation(spawnX, getHighestBlockYAt(spawnX, spawnZ), spawnZ);
}
}
// load up chunks around the spawn location
spawnChunkLock.clear();
if (keepSpawnLoaded) {
int centerX = spawnLocation.getBlockX() >> 4;
int centerZ = spawnLocation.getBlockZ() >> 4;
int radius = 4 * server.getViewDistance() / 3;
long loadTime = System.currentTimeMillis();
int total = (radius * 2 + 1) * (radius * 2 + 1), current = 0;
for (int x = centerX - radius; x <= centerX + radius; ++x) {
for (int z = centerZ - radius; z <= centerZ + radius; ++z) {
++current;
loadChunk(x, z);
spawnChunkLock.acquire(new GlowChunk.Key(x, z));
if (System.currentTimeMillis() >= loadTime + 1000) {
int progress = 100 * current / total;
GlowServer.logger.info("Preparing spawn for " + name + ": " + progress + "%");
loadTime = System.currentTimeMillis();
}
}
}
}
server.getLogger().info("Preparing spawn for " + name + ": done");
EventFactory.callEvent(new WorldLoadEvent(this));
}
////////////////////////////////////////////////////////////////////////////
// Various internal mechanisms
/**
* Get the world chunk manager.
* @return The ChunkManager for the world.
*/
public ChunkManager getChunkManager() {
return chunks;
}
/**
* Get the world's parent server.
* @return The GlowServer for the world.
*/
public GlowServer getServer() {
return server;
}
/**
* Get a new chunk lock object a player or other party can use to keep chunks loaded.
* @return The ChunkLock.
*/
public ChunkManager.ChunkLock newChunkLock(String desc) {
return new ChunkManager.ChunkLock(chunks, name + ": " + desc);
}
/**
* Updates all the entities within this world.
*/
public void pulse() {
List<GlowEntity> temp = new ArrayList<>(entities.getAll());
List<GlowEntity> players = new LinkedList<>();
// pulse players last so they actually see that other entities have
// moved. unfortunately pretty hacky. not a problem for players b/c
// their position is modified by session ticking.
for (GlowEntity entity : temp) {
if (entity instanceof GlowPlayer) {
players.add(entity);
} else {
entity.pulse();
}
}
for (GlowEntity entity : players) {
entity.pulse();
}
for (GlowEntity entity : temp) {
entity.reset();
}
// Tick the world age and time of day
// Modulus by 24000, the tick length of a day
worldAge++;
if (gameRules.getBoolean("doDaylightCycle")) {
time = (time + 1) % DAY_LENGTH;
}
if (worldAge % (30 * 20) == 0) {
// Only send the time every so often; clients are smart.
for (GlowPlayer player : getRawPlayers()) {
player.sendTime();
}
}
// only tick weather in a NORMAL world
if (environment == Environment.NORMAL) {
if (--rainingTicks <= 0) {
setStorm(!currentlyRaining);
}
if (--thunderingTicks <= 0) {
setThundering(!currentlyThundering);
}
updateWeather();
if (currentlyRaining && currentlyThundering) {
if (random.nextDouble() < .01) {
GlowChunk[] chunkList = chunks.getLoadedChunks();
if (chunkList.length > 0) {
GlowChunk chunk = chunkList[random.nextInt(chunkList.length)];
int x = (chunk.getX() << 4) + random.nextInt(16);
int z = (chunk.getZ() << 4) + random.nextInt(16);
int y = getHighestBlockYAt(x, z);
strikeLightning(new Location(this, x, y, z));
}
}
}
}
if (--saveTimer <= 0) {
saveTimer = AUTOSAVE_TIME;
chunks.unloadOldChunks();
if (autosave) {
save(true);
}
}
}
/**
* Calculates how much the rays from the location to the entity's bounding box is blocked.
* @param location The location for the rays to start
* @param entity The entity that's bounding box is the ray's end point
* @return a value between 0 and 1, where 0 = all rays blocked and 1 = all rays unblocked
*/
public float rayTrace(Location location, GlowEntity entity) {
// TODO: calculate how much of the entity is visible (not blocked by blocks) from the location
/*
* To calculate this step through the entity's bounding box and check whether the ray to the point
* in the bounding box is blocked.
*
* Return (unblockedRays / allRays)
*/
return 1;
}
/**
* Gets the entity manager.
* @return The entity manager.
*/
public EntityManager getEntityManager() {
return entities;
}
public Collection<GlowPlayer> getRawPlayers() {
return entities.getAll(GlowPlayer.class);
}
////////////////////////////////////////////////////////////////////////////
// Entity lists
@Override
public List<Player> getPlayers() {
return new ArrayList<Player>(entities.getAll(GlowPlayer.class));
}
@Override
public List<Entity> getEntities() {
return new ArrayList<Entity>(entities.getAll());
}
@Override
public List<LivingEntity> getLivingEntities() {
List<LivingEntity> result = new LinkedList<>();
for (Entity e : entities.getAll()) {
if (e instanceof GlowLivingEntity) result.add((GlowLivingEntity) e);
}
return result;
}
@Override
@Deprecated
@SuppressWarnings("unchecked")
public <T extends Entity> Collection<T> getEntitiesByClass(Class<T>... classes) {
return (Collection<T>) getEntitiesByClasses(classes);
}
@Override
@SuppressWarnings("unchecked")
public <T extends Entity> Collection<T> getEntitiesByClass(Class<T> cls) {
ArrayList<T> result = new ArrayList<>();
for (Entity e : entities.getAll()) {
if (cls.isAssignableFrom(e.getClass())) {
result.add((T) e);
}
}
return result;
}
@Override
public Collection<Entity> getEntitiesByClasses(Class<?>... classes) {
ArrayList<Entity> result = new ArrayList<>();
for (Entity e : entities.getAll()) {
for (Class<?> cls : classes) {
if (cls.isAssignableFrom(e.getClass())) {
result.add(e);
break;
}
}
}
return result;
}
////////////////////////////////////////////////////////////////////////////
// Various malleable world properties
@Override
public Location getSpawnLocation() {
return spawnLocation.clone();
}
@Override
public boolean setSpawnLocation(int x, int y, int z) {
Location oldSpawn = spawnLocation;
spawnLocation = new Location(this, x, y, z);
EventFactory.callEvent(new SpawnChangeEvent(this, oldSpawn));
return true;
}
@Override
public boolean getPVP() {
return pvpAllowed;
}
@Override
public void setPVP(boolean pvp) {
pvpAllowed = pvp;
}
@Override
public boolean getKeepSpawnInMemory() {
return keepSpawnLoaded;
}
@Override
public void setKeepSpawnInMemory(boolean keepLoaded) {
keepSpawnLoaded = keepLoaded;
// update the chunk lock as needed
spawnChunkLock.clear();
if (keepLoaded) {
int centerX = spawnLocation.getBlockX() >> 4;
int centerZ = spawnLocation.getBlockZ() >> 4;
int radius = 4 * server.getViewDistance() / 3;
for (int x = centerX - radius; x <= centerX + radius; ++x) {
for (int z = centerZ - radius; z <= centerZ + radius; ++z) {
loadChunk(x, z);
spawnChunkLock.acquire(new GlowChunk.Key(x, z));
}
}
} else {
// attempt to immediately unload the spawn
chunks.unloadOldChunks();
}
}
@Override
public boolean isAutoSave() {
return autosave;
}
@Override
public void setAutoSave(boolean value) {
autosave = value;
}
@Override
public Difficulty getDifficulty() {
return difficulty;
}
@Override
public void setDifficulty(Difficulty difficulty) {
this.difficulty = difficulty;
ServerDifficultyMessage message = new ServerDifficultyMessage(difficulty.getValue());
for (GlowPlayer player : getRawPlayers()) {
player.getSession().send(message);
}
}
////////////////////////////////////////////////////////////////////////////
// Entity spawning properties
@Override
public void setSpawnFlags(boolean allowMonsters, boolean allowAnimals) {
spawnMonsters = allowMonsters;
spawnAnimals = allowAnimals;
}
@Override
public boolean getAllowAnimals() {
return spawnAnimals;
}
@Override
public boolean getAllowMonsters() {
return spawnMonsters;
}
@Override
public long getTicksPerAnimalSpawns() {
return ticksPerAnimal;
}
@Override
public void setTicksPerAnimalSpawns(int ticksPerAnimalSpawns) {
ticksPerAnimal = ticksPerAnimalSpawns;
}
@Override
public long getTicksPerMonsterSpawns() {
return ticksPerMonster;
}
@Override
public void setTicksPerMonsterSpawns(int ticksPerMonsterSpawns) {
ticksPerMonster = ticksPerMonsterSpawns;
}
@Override
public int getMonsterSpawnLimit() {
return monsterLimit;
}
@Override
public void setMonsterSpawnLimit(int limit) {
monsterLimit = limit;
}
@Override
public int getAnimalSpawnLimit() {
return animalLimit;
}
@Override
public void setAnimalSpawnLimit(int limit) {
animalLimit = limit;
}
@Override
public int getWaterAnimalSpawnLimit() {
return waterAnimalLimit;
}
@Override
public void setWaterAnimalSpawnLimit(int limit) {
waterAnimalLimit = limit;
}
@Override
public int getAmbientSpawnLimit() {
return ambientLimit;
}
@Override
public void setAmbientSpawnLimit(int limit) {
ambientLimit = limit;
}
////////////////////////////////////////////////////////////////////////////
// Various fixed world properties
@Override
public Environment getEnvironment() {
return environment;
}
@Override
public long getSeed() {
return seed;
}
@Override
public UUID getUID() {
return uid;
}
@Override
public String getName() {
return name;
}
@Override
public int getMaxHeight() {
return GlowChunk.DEPTH;
}
@Override
public int getSeaLevel() {
return getMaxHeight() / 2;
}
@Override
public WorldType getWorldType() {
return worldType;
}
@Override
public boolean canGenerateStructures() {
return generateStructures;
}
////////////////////////////////////////////////////////////////////////////
// force-save
@Override
public void save() {
save(false);
}
public void save(boolean async) {
EventFactory.callEvent(new WorldSaveEvent(this));
// save metadata
writeWorldData(async);
// save chunks
maybeAsync(async, new Runnable() {
@Override
public void run() {
for (GlowChunk chunk : chunks.getLoadedChunks()) {
chunks.performSave(chunk);
}
}
});
// save players
for (GlowPlayer player : getRawPlayers()) {
player.saveData(async);
}
}
////////////////////////////////////////////////////////////////////////////
// map generation
@Override
public ChunkGenerator getGenerator() {
return chunks.getGenerator();
}
@Override
public List<BlockPopulator> getPopulators() {
return populators;
}
@Override
public boolean generateTree(Location location, TreeType type) {
return generateTree(location, type, null);
}
@Override
public boolean generateTree(Location loc, TreeType type, BlockChangeDelegate delegate) {
final BlockStateDelegate blockStateDelegate = new BlockStateDelegate();
final TreeGenerator generator = new TreeGenerator(blockStateDelegate);
if (generator.generate(random, loc, type)) {
final List<BlockState> blockStates = new ArrayList<>(blockStateDelegate.getBlockStates());
StructureGrowEvent growEvent = new StructureGrowEvent(loc, type, false, null, blockStates);
EventFactory.callEvent(growEvent);
if (!growEvent.isCancelled()) {
for (BlockState state : blockStates) {
state.update(true);
if (delegate != null) {
delegate.setTypeIdAndData(state.getX(), state.getY(), state.getZ(), state.getTypeId(), state.getRawData());
}
}
return true;
}
}
return false;
}
////////////////////////////////////////////////////////////////////////////
// get block, chunk, id, highest methods with coords
@Override
public GlowBlock getBlockAt(int x, int y, int z) {
return new GlowBlock(getChunkAt(x >> 4, z >> 4), x, y & 0xff, z);
}
@Override
public int getBlockTypeIdAt(int x, int y, int z) {
return getChunkAt(x >> 4, z >> 4).getType(x & 0xF, z & 0xF, y);
}
@Override
public int getHighestBlockYAt(int x, int z) {
return getChunkAt(x >> 4, z >> 4).getHeight(x & 0xf, z & 0xf);
}
@Override
public GlowChunk getChunkAt(int x, int z) {
return chunks.getChunk(x, z);
}
////////////////////////////////////////////////////////////////////////////
// get block, chunk, id, highest with locations
@Override
public GlowBlock getBlockAt(Location location) {
return getBlockAt(location.getBlockX(), location.getBlockY(), location.getBlockZ());
}
@Override
public int getBlockTypeIdAt(Location location) {
return getBlockTypeIdAt(location.getBlockX(), location.getBlockY(), location.getBlockZ());
}
@Override
public int getHighestBlockYAt(Location location) {
return getHighestBlockYAt(location.getBlockX(), location.getBlockZ());
}
@Override
public Block getHighestBlockAt(int x, int z) {
return getBlockAt(x, getHighestBlockYAt(x, z), z);
}
@Override
public Block getHighestBlockAt(Location location) {
return getBlockAt(location.getBlockX(), getHighestBlockYAt(location), location.getBlockZ());
}
@Override
public Chunk getChunkAt(Location location) {
return getChunkAt(location.getBlockX() >> 4, location.getBlockZ() >> 4);
}
@Override
public Chunk getChunkAt(Block block) {
return getChunkAt(block.getX() >> 4, block.getZ() >> 4);
}
////////////////////////////////////////////////////////////////////////////
// Chunk loading and unloading
@Override
public boolean isChunkLoaded(Chunk chunk) {
return chunk.isLoaded();
}
@Override
public boolean isChunkLoaded(int x, int z) {
return chunks.isChunkLoaded(x, z);
}
@Override
public boolean isChunkInUse(int x, int z) {
return chunks.isChunkInUse(x, z);
}
@Override
public Chunk[] getLoadedChunks() {
return chunks.getLoadedChunks();
}
@Override
public void loadChunk(Chunk chunk) {
chunk.load();
}
@Override
public void loadChunk(int x, int z) {
getChunkAt(x, z).load();
}
@Override
public boolean loadChunk(int x, int z, boolean generate) {
return getChunkAt(x, z).load(generate);
}
@Override
public boolean unloadChunk(Chunk chunk) {
return chunk.unload();
}
@Override
public boolean unloadChunk(int x, int z) {
return unloadChunk(x, z, true);
}
@Override
public boolean unloadChunk(int x, int z, boolean save) {
return unloadChunk(x, z, save, true);
}
@Override
public boolean unloadChunk(int x, int z, boolean save, boolean safe) {
return !isChunkLoaded(x, z) || getChunkAt(x, z).unload(save, safe);
}
@Override
public boolean unloadChunkRequest(int x, int z) {
return unloadChunkRequest(x, z, true);
}
@Override
public boolean unloadChunkRequest(final int x, final int z, final boolean safe) {
if (safe && isChunkInUse(x, z)) return false;
server.getScheduler().runTask(null, new Runnable() {
@Override
public void run() {
unloadChunk(x, z, safe);
}
});
return true;
}
@Override
public boolean regenerateChunk(int x, int z) {
if (!chunks.forceRegeneration(x, z)) return false;
refreshChunk(x, z);
return true;
}
@Override
public boolean refreshChunk(int x, int z) {
if (!isChunkLoaded(x, z)) {
return false;
}
GlowChunk.Key key = new GlowChunk.Key(x, z);
boolean result = false;
for (GlowPlayer player : getRawPlayers()) {
if (player.canSeeChunk(key)) {
player.getSession().send(getChunkAt(x, z).toMessage());
result = true;
}
}
return result;
}
@Override
public ChunkSnapshot getEmptyChunkSnapshot(int x, int z, boolean includeBiome, boolean includeBiomeTempRain) {
return new GlowChunkSnapshot.EmptySnapshot(x, z, this, includeBiome, includeBiomeTempRain);
}
////////////////////////////////////////////////////////////////////////////
// Biomes
@Override
public Biome getBiome(int x, int z) {
if (environment == Environment.THE_END) {
return Biome.SKY;
} else if (environment == Environment.NETHER) {
return Biome.HELL;
}
return GlowBiome.getBiome(getChunkAt(x >> 4, z >> 4).getBiome(x & 0xF, z & 0xF));
}
@Override
public void setBiome(int x, int z, Biome bio) {
getChunkAt(x >> 4, z >> 4).setBiome(x & 0xF, z & 0xF, GlowBiome.getId(bio));
}
@Override
public double getTemperature(int x, int z) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public double getHumidity(int x, int z) {
throw new UnsupportedOperationException("Not supported yet.");
}
////////////////////////////////////////////////////////////////////////////
// Entity spawning
@Override
public <T extends Entity> T spawn(Location location, Class<T> clazz) throws IllegalArgumentException {
GlowEntity entity = null;
if (TNTPrimed.class.isAssignableFrom(clazz)) {
entity = new GlowTNTPrimed(location, null);
}
if (entity != null) {
@SuppressWarnings("unchecked")
T result = (T) entity;
return result;
}
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public GlowItem dropItem(Location location, ItemStack item) {
return new GlowItem(location, item);
}
@Override
public GlowItem dropItemNaturally(Location location, ItemStack item) {
double xs = random.nextFloat() * 0.7F + (1.0F - 0.7F) * 0.5D;
double ys = random.nextFloat() * 0.7F + (1.0F - 0.7F) * 0.5D;
double zs = random.nextFloat() * 0.7F + (1.0F - 0.7F) * 0.5D;
location = location.clone().add(xs, ys, zs);
return dropItem(location, item);
}
@Override
public Arrow spawnArrow(Location location, Vector velocity, float speed, float spread) {
Arrow arrow = spawn(location, Arrow.class);
// Transformative magic
Vector randVec = new Vector(random.nextGaussian(), random.nextGaussian(), random.nextGaussian());
randVec.multiply(0.0075 * (double) spread);
velocity.normalize();
velocity.add(randVec);
velocity.multiply(speed);
// yaw = Math.atan2(x, z) * 180.0D / 3.1415927410125732D;
// pitch = Math.atan2(y, Math.sqrt(x * x + z * z)) * 180.0D / 3.1415927410125732D
arrow.setVelocity(velocity);
return arrow;
}
@Override
public FallingBlock spawnFallingBlock(Location location, Material material, byte data) throws IllegalArgumentException {
return null;
}
@Override
public FallingBlock spawnFallingBlock(Location location, int blockId, byte blockData) throws IllegalArgumentException {
return null;
}
@Override
public Entity spawnEntity(Location loc, EntityType type) {
return spawn(loc, type.getEntityClass());
}
@Override
@Deprecated
public LivingEntity spawnCreature(Location loc, EntityType type) {
return (LivingEntity) spawn(loc, type.getEntityClass());
}
@Override
@Deprecated
public LivingEntity spawnCreature(Location loc, CreatureType type) {
return (LivingEntity) spawn(loc, type.getEntityClass());
}
private GlowLightningStrike strikeLightningFireEvent(final Location loc, final boolean effect) {
final GlowLightningStrike strike = new GlowLightningStrike(loc, effect, random);
final LightningStrikeEvent event = new LightningStrikeEvent(this, strike);
if (EventFactory.callEvent(event).isCancelled()) {
return null;
}
return strike;
}
@Override
public GlowLightningStrike strikeLightning(Location loc) {
return strikeLightningFireEvent(loc, false);
}
@Override
public GlowLightningStrike strikeLightningEffect(Location loc) {
return strikeLightningFireEvent(loc, true);
}
////////////////////////////////////////////////////////////////////////////
// Time
@Override
public long getTime() {
return time;
}
@Override
public void setTime(long time) {
this.time = (time % DAY_LENGTH + DAY_LENGTH) % DAY_LENGTH;
for (GlowPlayer player : getRawPlayers()) {
player.sendTime();
}
}
@Override
public long getFullTime() {
return worldAge;
}
@Override
public void setFullTime(long time) {
worldAge = time;
}
////////////////////////////////////////////////////////////////////////////
// Weather
@Override
public boolean hasStorm() {
return currentlyRaining;
}
@Override
public void setStorm(boolean hasStorm) {
// call event
WeatherChangeEvent event = new WeatherChangeEvent(this, hasStorm);
if (EventFactory.callEvent(event).isCancelled()) {
return;
}
// change weather
boolean previouslyRaining = currentlyRaining;
currentlyRaining = hasStorm;
// Numbers borrowed from CraftBukkit.
if (currentlyRaining) {
setWeatherDuration(random.nextInt(12000) + 12000);
} else {
setWeatherDuration(random.nextInt(168000) + 12000);
}
// update players
if (previouslyRaining != currentlyRaining) {
for (GlowPlayer player : getRawPlayers()) {
player.sendWeather();
}
}
}
@Override
public int getWeatherDuration() {
return rainingTicks;
}
@Override
public void setWeatherDuration(int duration) {
rainingTicks = duration;
}
@Override
public boolean isThundering() {
return currentlyThundering;
}
@Override
public void setThundering(boolean thundering) {
// call event
ThunderChangeEvent event = new ThunderChangeEvent(this, thundering);
if (EventFactory.callEvent(event).isCancelled()) {
return;
}
// change weather
currentlyThundering = thundering;
// Numbers borrowed from CraftBukkit.
if (currentlyThundering) {
setThunderDuration(random.nextInt(12000) + 3600);
} else {
setThunderDuration(random.nextInt(168000) + 12000);
}
}
@Override
public int getThunderDuration() {
return thunderingTicks;
}
@Override
public void setThunderDuration(int duration) {
thunderingTicks = duration;
}
public float getRainDensity() {
return currentRainDensity;
}
public float getSkyDarkness() {
return currentSkyDarkness;
}
private void updateWeather() {
final float previousRainDensity = currentRainDensity;
final float previousSkyDarkness = currentSkyDarkness;
final float rainDensityModifier = currentlyRaining ? .01F : -.01F;
final float skyDarknessModifier = currentlyThundering ? .01F : -.01F;
currentRainDensity = Math.max(0, Math.min(1, previousRainDensity + rainDensityModifier));
currentSkyDarkness = Math.max(0, Math.min(1, previousSkyDarkness + skyDarknessModifier));
if (previousRainDensity != currentRainDensity) {
for (GlowPlayer player : getRawPlayers()) {
player.sendRainDensity();
}
}
if (previousSkyDarkness != currentSkyDarkness) {
for (GlowPlayer player : getRawPlayers()) {
player.sendSkyDarkness();
}
}
}
////////////////////////////////////////////////////////////////////////////
// Explosions
@Override
public boolean createExplosion(Location loc, float power) {
return createExplosion(loc, power, false);
}
@Override
public boolean createExplosion(Location loc, float power, boolean setFire) {
return createExplosion(loc.getX(), loc.getY(), loc.getZ(), power, setFire, true);
}
@Override
public boolean createExplosion(double x, double y, double z, float power) {
return createExplosion(x, y, z, power, false, true);
}
@Override
public boolean createExplosion(double x, double y, double z, float power, boolean setFire) {
return createExplosion(x, y, z, power, setFire, true);
}
@Override
public boolean createExplosion(double x, double y, double z, float power, boolean setFire, boolean breakBlocks) {
return createExplosion(null, x, y, z, power, setFire, breakBlocks);
}
/**
* Create an explosion with a specific entity as the source.
* @param source The entity to treat as the source, or null.
* @param x X coordinate
* @param y Y coordinate
* @param z Z coordinate
* @param power The power of explosion, where 4F is TNT
* @param incendiary Whether or not to set blocks on fire
* @param breakBlocks Whether or not to have blocks be destroyed
* @return false if explosion was canceled, otherwise true
*/
public boolean createExplosion(Entity source, double x, double y, double z, float power, boolean incendiary, boolean breakBlocks) {
Explosion explosion = new Explosion(source, this, x, y, z, power, incendiary, breakBlocks);
return explosion.explodeWithEvent();
}
////////////////////////////////////////////////////////////////////////////
// Effects
@Override
public void playEffect(Location location, Effect effect, int data) {
playEffect(location, effect, data, 64);
}
@Override
public void playEffect(Location location, Effect effect, int data, int radius) {
final int radiusSquared = radius * radius;
for (Player player : getRawPlayers()) {
if (player.getLocation().distanceSquared(location) <= radiusSquared) {
player.playEffect(location, effect, data);
}
}
}
@Override
public <T> void playEffect(Location location, Effect effect, T data) {
playEffect(location, effect, data, 64);
}
@Override
public <T> void playEffect(Location location, Effect effect, T data, int radius) {
playEffect(location, effect, GlowEffect.getDataValue(effect, data), radius);
}
public void playEffectExceptTo(Location location, Effect effect, int data, int radius, Player exclude) {
final int radiusSquared = radius * radius;
for (Player player : getRawPlayers()) {
if (!player.equals(exclude) && player.getLocation().distanceSquared(location) <= radiusSquared) {
player.playEffect(location, effect, data);
}
}
}
@Override
public void playSound(Location location, Sound sound, float volume, float pitch) {
if (location == null || sound == null) return;
final double radiusSquared = Math.pow(volume * 16, 2);
for (Player player : getRawPlayers()) {
if (player.getLocation().distanceSquared(location) <= radiusSquared) {
player.playSound(location, sound, volume, pitch);
}
}
}
@Override
public void showParticle(Location loc, Particle particle, float offsetX, float offsetY, float offsetZ, float speed, int amount) {
showParticle(loc, particle, null, offsetX, offsetY, offsetZ, speed, amount);
}
@Override
public void showParticle(Location loc, Particle particle, MaterialData material, float offsetX, float offsetY, float offsetZ, float speed, int amount) {
if (loc == null || particle == null) return;
final double radiusSquared;
if (GlowParticle.isLongDistance(particle)) {
radiusSquared = 48 * 48;
} else {
radiusSquared = 16 * 16;
}
for (Player player : getRawPlayers()) {
if (player.getLocation().distanceSquared(loc) <= radiusSquared) {
player.showParticle(loc, particle, material, offsetX, offsetY, offsetZ, speed, amount);
}
}
}
////////////////////////////////////////////////////////////////////////////
// Level data write
/**
* Save the world data using the metadata service.
* @param async Whether to write asynchronously.
*/
private void writeWorldData(boolean async) {
maybeAsync(async, new Runnable() {
@Override
public void run() {
try {
storageProvider.getMetadataService().writeWorldData();
} catch (IOException e) {
server.getLogger().severe("Could not save metadata for world: " + getName());
e.printStackTrace();
}
}
});
}
/**
* Execute a runnable, optionally asynchronously.
* @param async Whether to run the runnable in an asynchronous task.
* @param runnable The runnable to run.
*/
private void maybeAsync(boolean async, Runnable runnable) {
if (async) {
server.getScheduler().runTaskAsynchronously(null, runnable);
} else {
runnable.run();
}
}
/**
* Unloads the world
* @return true if successful
*/
public boolean unload() {
EventFactory.callEvent(new WorldUnloadEvent(this));
try {
storageProvider.getChunkIoService().unload();
} catch (IOException e) {
return false;
}
return true;
}
/**
* Get the storage provider for the world.
* @return The {@link WorldStorageProvider}.
*/
public WorldStorageProvider getStorage() {
return storageProvider;
}
/**
* Get the world folder.
* @return world folder
*/
@Override
public File getWorldFolder() {
return storageProvider.getFolder();
}
////////////////////////////////////////////////////////////////////////////
// Game rules
@Override
public String[] getGameRules() {
return gameRules.getKeys();
}
@Override
public String getGameRuleValue(String rule) {
return gameRules.getString(rule);
}
@Override
public boolean setGameRuleValue(String rule, String value) {
if (!gameRules.setValue(rule, value)) {
return false;
}
if (rule.equals("doDaylightCycle")) {
// inform clients about the daylight cycle change
for (GlowPlayer player : getRawPlayers()) {
player.sendTime();
}
} else if (rule.equals("reducedDebugInfo")) {
// inform clients about the debug info change
EntityStatusMessage message = new EntityStatusMessage(0, gameRules.getBoolean("reducedDebugInfo") ? EntityStatusMessage.ENABLE_REDUCED_DEBUG_INFO : EntityStatusMessage.DISABLE_REDUCED_DEBUG_INFO);
for (GlowPlayer player : getRawPlayers()) {
player.getSession().send(message);
}
}
return true;
}
@Override
public boolean isGameRule(String rule) {
return gameRules.isGameRule(rule);
}
public GameRuleManager getGameRuleMap() {
return gameRules;
}
////////////////////////////////////////////////////////////////////////////
// Metadata
@Override
public void setMetadata(String metadataKey, MetadataValue newMetadataValue) {
metadata.setMetadata(this, metadataKey, newMetadataValue);
}
@Override
public List<MetadataValue> getMetadata(String metadataKey) {
return metadata.getMetadata(this, metadataKey);
}
@Override
public boolean hasMetadata(String metadataKey) {
return metadata.hasMetadata(this, metadataKey);
}
@Override
public void removeMetadata(String metadataKey, Plugin owningPlugin) {
metadata.removeMetadata(this, metadataKey, owningPlugin);
}
////////////////////////////////////////////////////////////////////////////
// Plugin messages
@Override
public void sendPluginMessage(Plugin source, String channel, byte[] message) {
StandardMessenger.validatePluginMessage(server.getMessenger(), source, channel, message);
for (Player player : getRawPlayers()) {
player.sendPluginMessage(source, channel, message);
}
}
@Override
public Set<String> getListeningPluginChannels() {
HashSet<String> result = new HashSet<>();
for (Player player : getRawPlayers()) {
result.addAll(player.getListeningPluginChannels());
}
return result;
}
private void pulseTickMap() {
ItemTable itemTable = ItemTable.instance();
Map map = getTickMap();
for (Map.Entry entry : map.entrySet()) {
if (worldAge % entry.getValue() == 0) {
GlowBlock block = this.getBlockAt(entry.getKey());
BlockType notifyType = itemTable.getBlock(block.getTypeId());
if (notifyType != null)
notifyType.recievePulse(block);
}
private Map<Location, Integer> getTickMap() {
return new HashMap<>(tickMap);
}
public void requestPulse(GlowBlock block, int tickRate) {
Map<Location, Integer> map = getTickMap();
Location target = block.getLocation();
for (Location location : map.keySet()) {
if (target.equals(location)) {
if (tickRate > 0)
tickMap.put(location, tickRate);
else
tickMap.remove(location);
return;
}
}
if (tickRate > 0)
tickMap.put(target, tickRate);
}
public void cancelPulse(GlowBlock block) {
requestPulse(block, 0);
}
}