package net.glowstone; | |
import java.util.ArrayList; | |
import java.util.Collection; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Random; | |
import java.util.UUID; | |
import java.util.logging.Level; | |
import org.bukkit.BlockChangeDelegate; | |
import org.bukkit.Chunk; | |
import org.bukkit.ChunkSnapshot; | |
import org.bukkit.Effect; | |
import org.bukkit.Location; | |
import org.bukkit.TreeType; | |
import org.bukkit.World; | |
import org.bukkit.block.Biome; | |
import org.bukkit.block.Block; | |
import org.bukkit.entity.Arrow; | |
import org.bukkit.entity.CreatureType; | |
import org.bukkit.entity.Entity; | |
import org.bukkit.entity.Item; | |
import org.bukkit.entity.LivingEntity; | |
import org.bukkit.entity.Player; | |
import org.bukkit.generator.BlockPopulator; | |
import org.bukkit.generator.ChunkGenerator; | |
import org.bukkit.inventory.ItemStack; | |
import org.bukkit.util.Vector; | |
import net.glowstone.block.GlowBlock; | |
import net.glowstone.io.ChunkIoService; | |
import net.glowstone.entity.GlowEntity; | |
import net.glowstone.entity.EntityManager; | |
import net.glowstone.entity.GlowLightningStrike; | |
import net.glowstone.entity.GlowLivingEntity; | |
import net.glowstone.entity.GlowPlayer; | |
import net.glowstone.msg.LoadChunkMessage; | |
import net.glowstone.msg.StateChangeMessage; | |
import net.glowstone.msg.TimeMessage; | |
/** | |
* A class which represents the in-game world. | |
* @author Graham Edgecombe | |
*/ | |
public final class GlowWorld implements World { | |
/** | |
* 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; | |
/** | |
* The entity manager. | |
*/ | |
private final EntityManager entities = new EntityManager(); | |
/** | |
* This world's Random instance. | |
*/ | |
private final Random random = new Random(); | |
/** | |
* A map between locations and cached Block objects. | |
*/ | |
private final HashMap<Location, GlowBlock> blockCache = new HashMap<Location, GlowBlock>(); | |
/** | |
* The world populators for this world. | |
*/ | |
private final List<BlockPopulator> populators; | |
/** | |
* The environment. | |
*/ | |
private final Environment environment; | |
/** | |
* 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 current world time. | |
*/ | |
private long time = 0; | |
/** | |
* The time until the next full-save. | |
*/ | |
private int saveTimer = 0; | |
/** | |
* Creates a new world with the specified chunk I/O service, environment, | |
* and world generator. | |
* @param name The name of the world. | |
* @param service The chunk I/O service. | |
* @param environment The environment. | |
* @param generator The world generator. | |
*/ | |
public GlowWorld(GlowServer server, String name, Environment environment, long seed, ChunkIoService service, ChunkGenerator generator) { | |
this.server = server; | |
this.name = name; | |
this.environment = environment; | |
this.seed = seed; | |
chunks = new ChunkManager(this, service, generator); | |
populators = generator.getDefaultPopulators(this); | |
spawnLocation = generator.getFixedSpawnLocation(this, random); | |
int centerX = (spawnLocation == null) ? 0 : spawnLocation.getBlockX() >> 4; | |
int centerZ = (spawnLocation == null) ? 0 : spawnLocation.getBlockZ() >> 4; | |
server.getLogger().log(Level.INFO, "Preparing spawn for {0}", name); | |
long loadTime = System.currentTimeMillis(); | |
int radius = 4 * server.getViewDistance() / 3; | |
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; | |
chunks.getChunk(x, z); | |
if (System.currentTimeMillis() >= loadTime + 1000) { | |
int progress = 100 * current / total; | |
GlowServer.logger.log(Level.INFO, "Preparing spawn for {0}: {1}%", new Object[]{name, progress}); | |
loadTime = System.currentTimeMillis(); | |
} | |
} | |
} | |
server.getLogger().log(Level.INFO, "Preparing spawn for {0}: done", name); | |
if (spawnLocation == null) { | |
spawnLocation = new Location(this, 0, 128, 0); | |
if (!generator.canSpawn(this, spawnLocation.getBlockX(), spawnLocation.getBlockZ())) { | |
// 10 tries only to prevent a return false; bomb | |
for (int tries = 0; tries < 10 && !generator.canSpawn(this, spawnLocation.getBlockX(), spawnLocation.getBlockZ()); ++tries) { | |
spawnLocation.setX(spawnLocation.getX() + random.nextDouble() * 128 - 64); | |
spawnLocation.setZ(spawnLocation.getZ() + random.nextDouble() * 128 - 64); | |
} | |
} | |
spawnLocation.setY(1 + getHighestBlockYAt(spawnLocation.getBlockX(), spawnLocation.getBlockZ())); | |
} | |
setStorm(false); | |
setThundering(false); | |
} | |
//////////////////////////////////////// | |
// Various internal mechanisms | |
/** | |
* Updates all the entities within this world. | |
*/ | |
public void pulse() { | |
ArrayList<GlowEntity> temp = new ArrayList<GlowEntity>(entities.getAll()); | |
for (GlowEntity entity : temp) | |
entity.pulse(); | |
for (GlowEntity entity : temp) | |
entity.reset(); | |
// We currently tick at 1/4 the speed of regular MC | |
// Modulus by 12000 to force permanent day. | |
time = (time + 1) % 12000; | |
if (time % 12 == 0) { | |
// Only send the time every so often; clients are smart. | |
for (GlowPlayer player : getRawPlayers()) { | |
player.getSession().send(new TimeMessage(player.getPlayerTime())); | |
} | |
} | |
if (--rainingTicks <= 0) { | |
setStorm(!currentlyRaining); | |
} | |
if (--thunderingTicks <= 0) { | |
setThundering(!currentlyThundering); | |
} | |
if (currentlyRaining && currentlyThundering) { | |
if (random.nextDouble() < .01) { | |
GlowChunk[] chunkList = chunks.getLoadedChunks(); | |
GlowChunk chunk = chunkList[random.nextInt(chunkList.length)]; | |
int x = (chunk.getX() << 4) + (int)(random.nextDouble() * 16); | |
int z = (chunk.getZ() << 4) + (int)(random.nextDouble() * 16); | |
int y = getHighestBlockYAt(x, z); | |
strikeLightning(new Location(this, x, y, z)); | |
} | |
} | |
if (--saveTimer <= 0) { | |
saveTimer = 60 * 20; | |
save(); | |
} | |
} | |
/** | |
* Gets the entity manager. | |
* @return The entity manager. | |
*/ | |
public EntityManager getEntityManager() { | |
return entities; | |
} | |
public Collection<GlowPlayer> getRawPlayers() { | |
return entities.getAll(GlowPlayer.class); | |
} | |
// GlowEntity lists | |
public List<Player> getPlayers() { | |
Collection<GlowPlayer> players = entities.getAll(GlowPlayer.class); | |
ArrayList<Player> result = new ArrayList<Player>(); | |
for (Player p : players) { | |
result.add(p); | |
} | |
return result; | |
} | |
public List<Entity> getEntities() { | |
Collection<GlowEntity> list = entities.getAll(); | |
ArrayList<Entity> result = new ArrayList<Entity>(); | |
for (Entity e : list) { | |
result.add(e); | |
} | |
return result; | |
} | |
public List<LivingEntity> getLivingEntities() { | |
Collection<GlowEntity> list = entities.getAll(); | |
ArrayList<LivingEntity> result = new ArrayList<LivingEntity>(); | |
for (Entity e : list) { | |
if (e instanceof GlowLivingEntity) result.add((GlowLivingEntity) e); | |
} | |
return result; | |
} | |
// Various malleable world properties | |
public Location getSpawnLocation() { | |
return spawnLocation; | |
} | |
public boolean setSpawnLocation(int x, int y, int z) { | |
spawnLocation = new Location(this, x, y, z); | |
return true; | |
} | |
public boolean getPVP() { | |
return pvpAllowed; | |
} | |
public void setPVP(boolean pvp) { | |
pvpAllowed = pvp; | |
} | |
public void setSpawnFlags(boolean allowMonsters, boolean allowAnimals) { | |
spawnMonsters = allowMonsters; | |
spawnAnimals = allowAnimals; | |
} | |
public boolean getAllowAnimals() { | |
return spawnAnimals; | |
} | |
public boolean getAllowMonsters() { | |
return spawnMonsters; | |
} | |
// various fixed world properties | |
public Environment getEnvironment() { | |
return environment; | |
} | |
public long getSeed() { | |
return seed; | |
} | |
public UUID getUID() { | |
throw new UnsupportedOperationException("Not supported yet."); | |
} | |
public String getName() { | |
return name; | |
} | |
public long getId() { | |
return (getSeed() + "_" + getName()).hashCode(); | |
} | |
public int getMaxHeight() { | |
return 128; | |
} | |
// force-save | |
public void save() { | |
for (GlowChunk chunk : chunks.getLoadedChunks()) { | |
chunks.forceSave(chunk.getX(), chunk.getZ()); | |
} | |
} | |
// map generation | |
public ChunkGenerator getGenerator() { | |
return chunks.getGenerator(); | |
} | |
public List<BlockPopulator> getPopulators() { | |
return populators; | |
} | |
public boolean generateTree(Location location, TreeType type) { | |
throw new UnsupportedOperationException("Not supported yet."); | |
} | |
public boolean generateTree(Location loc, TreeType type, BlockChangeDelegate delegate) { | |
throw new UnsupportedOperationException("Not supported yet."); | |
} | |
// get block, chunk, id, highest methods with coords | |
public GlowBlock getBlockAt(int x, int y, int z) { | |
Location blockLoc = new Location(this, x, y, z); | |
if (blockCache.containsKey(blockLoc)) { | |
return blockCache.get(blockLoc); | |
} else { | |
GlowBlock block = new GlowBlock(getChunkAt(x >> 4, z >> 4), x, y, z); | |
blockCache.put(blockLoc, block); | |
return block; | |
} | |
} | |
public int getBlockTypeIdAt(int x, int y, int z) { | |
return ((GlowChunk) getChunkAt(x >> 4, z >> 4)).getType(x & 0xF, z & 0xF, y & 0x7F); | |
} | |
public int getHighestBlockYAt(int x, int z) { | |
for (int y = GlowChunk.DEPTH - 1; y >= 0; --y) { | |
if (getBlockTypeIdAt(x, y, z) != 0) { | |
return y; | |
} | |
} | |
return 0; | |
} | |
public GlowChunk getChunkAt(int x, int z) { | |
return chunks.getChunk(x, z); | |
} | |
// get block, chunk, id, highest with locations | |
public GlowBlock getBlockAt(Location location) { | |
return getBlockAt(location.getBlockX(), location.getBlockY(), location.getBlockZ()); | |
} | |
public int getBlockTypeIdAt(Location location) { | |
return getBlockTypeIdAt(location.getBlockX(), location.getBlockY(), location.getBlockZ()); | |
} | |
public int getHighestBlockYAt(Location location) { | |
return getHighestBlockYAt(location.getBlockX(), location.getBlockZ()); | |
} | |
public Block getHighestBlockAt(int x, int z) { | |
return getBlockAt(x, getHighestBlockYAt(x, z), z); | |
} | |
public Block getHighestBlockAt(Location location) { | |
return getBlockAt(location.getBlockX(), getHighestBlockYAt(location), location.getBlockZ()); | |
} | |
public Chunk getChunkAt(Location location) { | |
return getChunkAt(location.getBlockX(), location.getBlockZ()); | |
} | |
public Chunk getChunkAt(Block block) { | |
return getChunkAt(block.getX(), block.getZ()); | |
} | |
// Chunk loading and unloading | |
public boolean isChunkLoaded(Chunk chunk) { | |
return isChunkLoaded(chunk.getX(), chunk.getZ()); | |
} | |
public boolean isChunkLoaded(int x, int z) { | |
return chunks.isLoaded(x, z); | |
} | |
public Chunk[] getLoadedChunks() { | |
return chunks.getLoadedChunks(); | |
} | |
public void loadChunk(Chunk chunk) { | |
loadChunk(chunk.getX(), chunk.getZ()); | |
} | |
public void loadChunk(int x, int z) { | |
// Force load by getting chunk. | |
chunks.getChunk(x, z); | |
} | |
public boolean loadChunk(int x, int z, boolean generate) { | |
if (generate) { | |
throw new UnsupportedOperationException("Not supported yet."); | |
} else { | |
loadChunk(x, z); | |
return true; | |
} | |
} | |
public boolean unloadChunk(int x, int z) { | |
return unloadChunk(x, z, true); | |
} | |
public boolean unloadChunk(int x, int z, boolean save) { | |
return chunks.unloadChunk(x, z, save); | |
} | |
public boolean unloadChunk(int x, int z, boolean save, boolean safe) { | |
if (!safe) { | |
throw new UnsupportedOperationException("unloadChunk does not yet support unsafe unloading."); | |
} | |
return unloadChunk(x, z, save); | |
} | |
public boolean unloadChunk(Chunk chunk) { | |
return unloadChunk(chunk.getX(), chunk.getZ()); | |
} | |
public boolean unloadChunkRequest(int x, int z) { | |
return unloadChunkRequest(x, z, true); | |
} | |
public boolean unloadChunkRequest(int x, int z, boolean safe) { | |
throw new UnsupportedOperationException("Not supported yet."); | |
} | |
public boolean regenerateChunk(int x, int z) { | |
if (!chunks.forceRegeneration(x, z)) return false; | |
refreshChunk(x, z); | |
return true; | |
} | |
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 (Player p : getPlayers()) { | |
GlowPlayer player = (GlowPlayer) p; | |
if (player.canSee(key)) { | |
player.getSession().send(new LoadChunkMessage(x, z, false)); | |
player.getSession().send(new LoadChunkMessage(x, z, true)); | |
player.getSession().send(getChunkAt(x, z).toMessage()); | |
result = true; | |
} | |
} | |
return result; | |
} | |
// biomes | |
public Biome getBiome(int x, int z) { | |
// TODO: Biomes | |
return Biome.PLAINS; | |
} | |
public double getTemperature(int x, int z) { | |
throw new UnsupportedOperationException("Not supported yet."); | |
} | |
public double getHumidity(int x, int z) { | |
throw new UnsupportedOperationException("Not supported yet."); | |
} | |
// entity spawning | |
public <T extends Entity> T spawn(Location location, Class<T> clazz) throws IllegalArgumentException { | |
throw new UnsupportedOperationException("Not supported yet."); | |
} | |
public Item dropItem(Location location, ItemStack item) { | |
// TODO: maybe spawn special due to item-ness? | |
Item itemEntity = spawn(location, Item.class); | |
itemEntity.setItemStack(item); | |
return itemEntity; | |
} | |
public Item 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(new Location(this, xs, ys, zs)); | |
return dropItem(location, item); | |
} | |
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.007499999832361937D * (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; | |
} | |
public LivingEntity spawnCreature(Location loc, CreatureType type) { | |
switch (type) { | |
case CHICKEN: | |
return spawn(loc, org.bukkit.entity.Chicken.class); | |
case COW: | |
return spawn(loc, org.bukkit.entity.Cow.class); | |
case CREEPER: | |
return spawn(loc, org.bukkit.entity.Creeper.class); | |
case GHAST: | |
return spawn(loc, org.bukkit.entity.Ghast.class); | |
case GIANT: | |
return spawn(loc, org.bukkit.entity.Giant.class); | |
case MONSTER: | |
return spawn(loc, org.bukkit.entity.Monster.class); | |
case PIG: | |
return spawn(loc, org.bukkit.entity.Pig.class); | |
case PIG_ZOMBIE: | |
return spawn(loc, org.bukkit.entity.PigZombie.class); | |
case SHEEP: | |
return spawn(loc, org.bukkit.entity.Sheep.class); | |
case SKELETON: | |
return spawn(loc, org.bukkit.entity.Skeleton.class); | |
case SLIME: | |
return spawn(loc, org.bukkit.entity.Slime.class); | |
case SPIDER: | |
return spawn(loc, org.bukkit.entity.Spider.class); | |
case SQUID: | |
return spawn(loc, org.bukkit.entity.Squid.class); | |
case ZOMBIE: | |
return spawn(loc, org.bukkit.entity.Zombie.class); | |
case WOLF: | |
return spawn(loc, org.bukkit.entity.Wolf.class); | |
default: | |
throw new IllegalArgumentException(); | |
} | |
} | |
public GlowLightningStrike strikeLightning(Location loc) { | |
GlowLightningStrike strike = new GlowLightningStrike(server, this, false); | |
strike.teleport(loc); | |
return strike; | |
} | |
public GlowLightningStrike strikeLightningEffect(Location loc) { | |
GlowLightningStrike strike = new GlowLightningStrike(server, this, true); | |
strike.teleport(loc); | |
return strike; | |
} | |
// Time related methods | |
public long getTime() { | |
return time; | |
} | |
public void setTime(long time) { | |
if (time < 0) time = (time % 24000) + 24000; | |
if (time > 24000) time %= 24000; | |
this.time = time; | |
} | |
public long getFullTime() { | |
return getTime(); | |
} | |
public void setFullTime(long time) { | |
setTime(time); | |
} | |
// Weather related methods | |
public boolean hasStorm() { | |
return currentlyRaining; | |
} | |
public void setStorm(boolean hasStorm) { | |
currentlyRaining = hasStorm; | |
// Numbers borrowed from CraftBukkit. | |
if (currentlyRaining) { | |
setWeatherDuration(random.nextInt(12000) + 12000); | |
} else { | |
setWeatherDuration(random.nextInt(168000) + 12000); | |
} | |
for (GlowPlayer player : getRawPlayers()) { | |
player.getSession().send(new StateChangeMessage((byte)(currentlyRaining ? 1 : 2))); | |
} | |
} | |
public int getWeatherDuration() { | |
return rainingTicks; | |
} | |
public void setWeatherDuration(int duration) { | |
rainingTicks = duration; | |
} | |
public boolean isThundering() { | |
return currentlyThundering; | |
} | |
public void setThundering(boolean thundering) { | |
currentlyThundering = thundering; | |
// Numbers borrowed from CraftBukkit. | |
if (currentlyThundering) { | |
setThunderDuration(random.nextInt(12000) + 3600); | |
} else { | |
setThunderDuration(random.nextInt(168000) + 12000); | |
} | |
} | |
public int getThunderDuration() { | |
return thunderingTicks; | |
} | |
public void setThunderDuration(int duration) { | |
thunderingTicks = duration; | |
} | |
// explosions | |
public boolean createExplosion(Location loc, float power, boolean setFire) { | |
throw new UnsupportedOperationException("Not supported yet."); | |
} | |
public boolean createExplosion(Location loc, float power) { | |
return createExplosion(loc, power, false); | |
} | |
public boolean createExplosion(double x, double y, double z, float power, boolean setFire) { | |
return createExplosion(new Location(this, x, y, z), power, setFire); | |
} | |
public boolean createExplosion(double x, double y, double z, float power) { | |
return createExplosion(new Location(this, x, y, z), power, false); | |
} | |
// effects | |
public void playEffect(Location location, Effect effect, int data) { | |
playEffect(location, effect, data, 64); | |
} | |
public void playEffect(Location location, Effect effect, int data, int radius) { | |
for (Player player : getPlayers()) { | |
if (player.getLocation().distance(location) <= radius) { | |
player.playEffect(location, effect, data); | |
} | |
} | |
} | |
public void playEffectExceptTo(Location location, Effect effect, int data, int radius, Player exclude) { | |
for (Player player : getPlayers()) { | |
if (!player.equals(exclude) && player.getLocation().distance(location) <= radius) { | |
player.playEffect(location, effect, data); | |
} | |
} | |
} | |
// misc | |
public ChunkSnapshot getEmptyChunkSnapshot(int x, int z, boolean includeBiome, boolean includeBiomeTempRain) { | |
return new GlowChunkSnapshot.EmptySnapshot(x, z, this, includeBiome, includeBiomeTempRain); | |
} | |
public boolean getKeepSpawnInMemory() { | |
return keepSpawnLoaded; | |
} | |
public void setKeepSpawnInMemory(boolean keepLoaded) { | |
keepSpawnLoaded = keepLoaded; | |
} | |
} |