Java tutorial
/* * This file is part of Spout. * * Copyright (c) 2011-2012, SpoutDev <http://www.spout.org/> * Spout is licensed under the SpoutDev License Version 1. * * Spout is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * In addition, 180 days after any changes are published, you can use the * software, incorporating those changes, under the terms of the MIT license, * as described in the SpoutDev License Version 1. * * Spout is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License, * the MIT license and the SpoutDev License Version 1 along with this program. * If not, see <http://www.gnu.org/licenses/> for the GNU Lesser General Public * License and see <http://www.spout.org/SpoutDevLicenseV1.txt> for the full license, * including the MIT license. */ package org.spout.engine.world; import java.io.File; import java.io.InputStream; import java.io.OutputStream; import java.io.Serializable; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.spout.api.Spout; import org.spout.api.collision.BoundingBox; import org.spout.api.collision.CollisionModel; import org.spout.api.collision.CollisionVolume; import org.spout.api.component.Component; import org.spout.api.component.ComponentHolder; import org.spout.api.component.WorldComponentHolder; import org.spout.api.component.components.BlockComponent; import org.spout.api.component.components.EntityComponent; import org.spout.api.entity.Entity; import org.spout.api.entity.EntityPrefab; import org.spout.api.entity.Player; import org.spout.api.entity.spawn.SpawnArrangement; import org.spout.api.event.Cause; import org.spout.api.event.block.CuboidChangeEvent; import org.spout.api.event.entity.EntitySpawnEvent; import org.spout.api.generator.WorldGenerator; import org.spout.api.generator.biome.Biome; import org.spout.api.generator.biome.BiomeGenerator; import org.spout.api.generator.biome.BiomeManager; import org.spout.api.geo.LoadOption; import org.spout.api.geo.World; import org.spout.api.geo.cuboid.Chunk; import org.spout.api.geo.cuboid.ChunkSnapshot; import org.spout.api.geo.cuboid.Region; import org.spout.api.geo.discrete.Point; import org.spout.api.geo.discrete.Transform; import org.spout.api.io.bytearrayarray.BAAWrapper; import org.spout.api.map.DefaultedMap; import org.spout.api.material.BlockMaterial; import org.spout.api.material.DynamicUpdateEntry; import org.spout.api.material.range.EffectRange; import org.spout.api.math.MathHelper; import org.spout.api.math.Quaternion; import org.spout.api.math.Vector3; import org.spout.api.model.Model; import org.spout.api.scheduler.TaskManager; import org.spout.api.util.StringMap; import org.spout.api.util.cuboid.CuboidBlockMaterialBuffer; import org.spout.api.util.hashing.IntPairHashed; import org.spout.api.util.hashing.NibblePairHashed; import org.spout.api.util.list.concurrent.ConcurrentList; import org.spout.api.util.map.concurrent.TSyncIntPairObjectHashMap; import org.spout.api.util.map.concurrent.TSyncLongObjectHashMap; import org.spout.api.util.sanitation.StringSanitizer; import org.spout.api.util.thread.LiveRead; import org.spout.api.util.thread.Threadsafe; import org.spout.engine.SpoutEngine; import org.spout.engine.entity.SpoutEntity; import org.spout.engine.filesystem.SharedFileSystem; import org.spout.engine.filesystem.WorldData; import org.spout.engine.scheduler.SpoutParallelTaskManager; import org.spout.engine.scheduler.SpoutScheduler; import org.spout.engine.scheduler.SpoutTaskManager; import org.spout.engine.util.thread.AsyncExecutor; import org.spout.engine.util.thread.AsyncManager; import org.spout.engine.util.thread.ThreadAsyncExecutor; import org.spout.engine.util.thread.snapshotable.SnapshotManager; import org.spout.engine.util.thread.snapshotable.SnapshotableLong; public class SpoutWorld extends AsyncManager implements World { private SnapshotManager snapshotManager = new SnapshotManager(); /** * The server of this world. */ private final SpoutEngine engine; /** * The name of this world. */ private final String name; /** * The world's UUID. */ private final UUID uid; /** * String item map, used to convert local id's to the server id */ private final StringMap itemMap; /** * The region source */ private final RegionSource regions; /** * The world seed. */ private final long seed; /** * The spawn position. */ private final Transform spawnLocation = new Transform(); /** * The current world age. */ private SnapshotableLong age; /** * The generator responsible for generating chunks in this world. */ private final WorldGenerator generator; /** * A set of all players currently connected to this world */ private final List<Player> players = new ConcurrentList<Player>(); /** * A map of the loaded columns */ private final TSyncLongObjectHashMap<SpoutColumn> columns = new TSyncLongObjectHashMap<SpoutColumn>(); private final Set<SpoutColumn> columnSet = new LinkedHashSet<SpoutColumn>(); /** * A map of column height map files */ private final TSyncIntPairObjectHashMap<BAAWrapper> heightMapBAAs; /** * The directory where the world data is stored */ private final File worldDirectory; /** * The async thread which handles the calculation of block and sky lighting in the world */ private final SpoutWorldLighting lightingManager; /** * The parallel task manager. This is used for submitting tasks to all regions in the world. */ protected final SpoutParallelTaskManager parallelTaskManager; private final SpoutTaskManager taskManager; /** * The sky light level the sky emits */ private byte skyLightLevel = 15; /** * Hashcode cache */ private final int hashcode; /** * Indicates if the snapshot queue for the renderer should be populated */ private final AtomicBoolean renderQueueEnabled = new AtomicBoolean(false); /** * RegionFile manager for the world */ private final RegionFileManager regionFileManager; /* * A WeakReference to this world */ private final WeakReference<World> selfReference; public static final WeakReference<World> NULL_WEAK_REFERENCE = new WeakReference<World>(null); Model skydome; /* * Components */ private final WorldComponentHolder componentHolder; // TODO set up number of stages ? public SpoutWorld(String name, SpoutEngine engine, long seed, long age, WorldGenerator generator, UUID uid, StringMap itemMap) { super(1, new ThreadAsyncExecutor(toString(name, uid, age)), engine); this.engine = engine; if (!StringSanitizer.isAlphaNumericUnderscore(name)) { name = Long.toHexString(System.currentTimeMillis()); Spout.getEngine().getLogger() .severe("World name " + name + " is not valid, using " + name + " instead"); } this.name = name; this.uid = uid; this.itemMap = itemMap; this.seed = seed; this.generator = generator; regions = new RegionSource(this, snapshotManager); worldDirectory = new File(SharedFileSystem.WORLDS_DIRECTORY, name); worldDirectory.mkdirs(); regionFileManager = new RegionFileManager(worldDirectory); heightMapBAAs = new TSyncIntPairObjectHashMap<BAAWrapper>(); this.hashcode = new HashCodeBuilder(27, 971).append(uid).toHashCode(); this.lightingManager = new SpoutWorldLighting(this); this.lightingManager.start(); parallelTaskManager = new SpoutParallelTaskManager(engine.getScheduler(), this); AsyncExecutor e = getExecutor(); Thread t; if (e instanceof Thread) { t = (Thread) e; } else { throw new IllegalStateException("AsyncExecutor should be instance of Thread"); } this.age = new SnapshotableLong(snapshotManager, age); taskManager = new SpoutTaskManager(getEngine().getScheduler(), false, t, age); spawnLocation.set(new Transform(new Point(this, 1, 100, 1), Quaternion.IDENTITY, Vector3.ONE)); selfReference = new WeakReference<World>(this); componentHolder = new WorldComponentHolder(this); } @Override public String getName() { return name; } @Override public UUID getUID() { return uid; } @Override public long getAge() { return age.get(); } @Override public SpoutBlock getBlock(int x, int y, int z) { return new SpoutBlock(this, x, y, z); } @Override public SpoutBlock getBlock(float x, float y, float z) { return this.getBlock(MathHelper.floor(x), MathHelper.floor(y), MathHelper.floor(z)); } @Override public SpoutBlock getBlock(Vector3 position) { return this.getBlock(position.getX(), position.getY(), position.getZ()); } @Override public SpoutRegion getRegion(int x, int y, int z, LoadOption loadopt) { return regions.getRegion(x, y, z, loadopt); } @Override public SpoutChunk getChunk(int x, int y, int z, LoadOption loadopt) { SpoutRegion region = getRegionFromChunk(x, y, z, loadopt); if (region != null) { return region.getChunk(x, y, z, loadopt); } return null; } @Override public Biome getBiome(int x, int y, int z) { if (y < 0 || y > getHeight()) { return null; } if (!(generator instanceof BiomeGenerator)) { return null; } final SpoutColumn column = getColumn(x, z, true); final BiomeManager manager = column.getBiomeManager(); if (manager != null) { final Biome biome = column.getBiomeManager().getBiome(x & SpoutColumn.BLOCKS.MASK, y & SpoutColumn.BLOCKS.MASK, z & SpoutColumn.BLOCKS.MASK); if (biome != null) { return biome; } } return ((BiomeGenerator) generator).getBiome(x, y, z, seed); } @Override public BiomeManager getBiomeManager(int x, int z) { return getBiomeManager(x, z, false); } @Override public BiomeManager getBiomeManager(int x, int z, boolean load) { final SpoutColumn column = getColumn(x, z, load); if (column != null) { return column.getBiomeManager(); } return null; } @Override public SpoutRegion getRegion(int x, int y, int z) { return getRegion(x, y, z, LoadOption.LOAD_GEN); } @Override public SpoutRegion getRegionFromChunk(int x, int y, int z) { return getRegionFromChunk(x, y, z, LoadOption.LOAD_GEN); } @Override public SpoutRegion getRegionFromChunk(int x, int y, int z, LoadOption loadopt) { return getRegion(x >> Region.CHUNKS.BITS, y >> Region.CHUNKS.BITS, z >> Region.CHUNKS.BITS, loadopt); } @Override public SpoutRegion getRegionFromBlock(Vector3 position) { return getRegionFromBlock(position, LoadOption.LOAD_GEN); } @Override public SpoutRegion getRegionFromBlock(Vector3 position, LoadOption loadopt) { return this.getRegionFromBlock(position.getFloorX(), position.getFloorY(), position.getFloorZ(), loadopt); } @Override public SpoutRegion getRegionFromBlock(int x, int y, int z) { return getRegionFromBlock(x, y, z, LoadOption.LOAD_GEN); } @Override public SpoutRegion getRegionFromBlock(int x, int y, int z, LoadOption loadopt) { return getRegion(x >> Region.BLOCKS.BITS, y >> Region.BLOCKS.BITS, z >> Region.BLOCKS.BITS, loadopt); } @Override public SpoutChunk getChunk(int x, int y, int z) { return this.getChunk(x, y, z, LoadOption.LOAD_GEN); } @Override public SpoutChunk getChunkFromBlock(int x, int y, int z) { return this.getChunkFromBlock(x, y, z, LoadOption.LOAD_GEN); } @Override public SpoutChunk getChunkFromBlock(int x, int y, int z, LoadOption loadopt) { return this.getChunk(x >> Chunk.BLOCKS.BITS, y >> Chunk.BLOCKS.BITS, z >> Chunk.BLOCKS.BITS, loadopt); } @Override public SpoutChunk getChunkFromBlock(Vector3 position) { return this.getChunkFromBlock(position, LoadOption.LOAD_GEN); } @Override public SpoutChunk getChunkFromBlock(Vector3 position, LoadOption loadopt) { return this.getChunkFromBlock(position.getFloorX(), position.getFloorY(), position.getFloorZ(), loadopt); } @Override public boolean setBlockMaterial(int x, int y, int z, BlockMaterial material, short data, Cause<?> cause) { return this.getChunkFromBlock(x, y, z).setBlockMaterial(x, y, z, material, data, cause); } @Override public boolean setBlockData(int x, int y, int z, short data, Cause<?> cause) { return getChunkFromBlock(x, y, z).setBlockData(x, y, z, data, cause); } @Override public boolean addBlockData(int x, int y, int z, short data, Cause<?> cause) { return getChunkFromBlock(x, y, z).addBlockData(x, y, z, data, cause); } @Override public int getBlockFullState(int x, int y, int z) { return getChunkFromBlock(x, y, z).getBlockFullState(x, y, z); } @Override public BlockMaterial getBlockMaterial(int x, int y, int z) { return getChunkFromBlock(x, y, z).getBlockMaterial(x, y, z); } @Override public short getBlockData(int x, int y, int z) { return getChunkFromBlock(x, y, z).getBlockData(x, y, z); } @Override public byte getBlockSkyLight(int x, int y, int z) { return getChunkFromBlock(x, y, z).getBlockSkyLight(x, y, z); } @Override public byte getBlockSkyLightRaw(int x, int y, int z) { return getChunkFromBlock(x, y, z).getBlockSkyLightRaw(x, y, z); } @Override public byte getBlockLight(int x, int y, int z) { return getChunkFromBlock(x, y, z).getBlockLight(x, y, z); } @Override public boolean compareAndSetData(int x, int y, int z, int expect, short data, Cause<?> cause) { return getChunkFromBlock(x, y, z).compareAndSetData(x, y, z, expect, data, cause); } @Override public short setBlockDataBits(int x, int y, int z, int bits, boolean set, Cause<?> cause) { return getChunkFromBlock(x, y, z).setBlockDataBits(x, y, z, bits, set, cause); } @Override public short setBlockDataBits(int x, int y, int z, int bits, Cause<?> cause) { return getChunkFromBlock(x, y, z).setBlockDataBits(x, y, z, bits, cause); } @Override public short clearBlockDataBits(int x, int y, int z, int bits, Cause<?> cause) { return getChunkFromBlock(x, y, z).clearBlockDataBits(x, y, z, bits, cause); } @Override public int getBlockDataField(int x, int y, int z, int bits) { return getChunkFromBlock(x, y, z).getBlockDataField(x, y, z, bits); } @Override public boolean isBlockDataBitSet(int x, int y, int z, int bits) { return getChunkFromBlock(x, y, z).isBlockDataBitSet(x, y, z, bits); } @Override public int setBlockDataField(int x, int y, int z, int bits, int value, Cause<?> cause) { return getChunkFromBlock(x, y, z).setBlockDataField(x, y, z, bits, value, cause); } @Override public int addBlockDataField(int x, int y, int z, int bits, int value, Cause<?> cause) { return getChunkFromBlock(x, y, z).addBlockDataField(x, y, z, bits, value, cause); } @Override public void resetDynamicBlock(int x, int y, int z) { this.getRegionFromBlock(x, y, z).resetDynamicBlock(x, y, z); } @Override public void syncResetDynamicBlock(int x, int y, int z) { this.getRegionFromBlock(x, y, z).syncResetDynamicBlock(x, y, z); } @Override public DynamicUpdateEntry queueDynamicUpdate(int x, int y, int z, long nextUpdate, int data) { return this.getRegionFromBlock(x, y, z).queueDynamicUpdate(x, y, z, nextUpdate, data); } @Override public DynamicUpdateEntry queueDynamicUpdate(int x, int y, int z, long nextUpdate) { return this.getRegionFromBlock(x, y, z).queueDynamicUpdate(x, y, z, nextUpdate); } @Override public DynamicUpdateEntry queueDynamicUpdate(int x, int y, int z) { return this.getRegionFromBlock(x, y, z).queueDynamicUpdate(x, y, z); } public StringMap getItemMap() { return itemMap; } @Override public int hashCode() { return hashcode; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } else if (!(obj instanceof SpoutWorld)) { return false; } else { SpoutWorld world = (SpoutWorld) obj; return world.getUID().equals(getUID()); } } @Override public int getSurfaceHeight(int x, int z, boolean load) { SpoutColumn column = getColumn(x, z, load); if (column == null) { return Integer.MIN_VALUE; } return column.getSurfaceHeight(x, z); } @Override public int getSurfaceHeight(int x, int z) { return getSurfaceHeight(x, z, false); } @Override public BlockMaterial getTopmostBlock(int x, int z, boolean load) { SpoutColumn column = getColumn(x, z, load); if (column == null) { return null; } return column.getTopmostBlock(x, z); } @Override public BlockMaterial getTopmostBlock(int x, int z) { return getTopmostBlock(x, z, false); } @Override public Entity createEntity(Point point, Class<? extends Component> type) { SpoutEntity entity = new SpoutEntity(point); entity.add(type); return entity; } @Override public Entity createEntity(Point point, EntityPrefab prefab) { SpoutEntity entity = new SpoutEntity(point); for (Class<? extends EntityComponent> c : prefab.getComponents()) entity.add(c); return entity; } /** * Spawns an entity into the world. Fires off a cancellable EntitySpawnEvent */ @Override public void spawnEntity(Entity e) { if (e.isSpawned()) { throw new IllegalArgumentException("Cannot spawn an entity that is already spawned!"); } SpoutRegion region = (SpoutRegion) e.getRegion(); if (region == null) { throw new IllegalStateException("Cannot spawn an entity that has a null region!"); } if (region.getEntityManager().isSpawnable((SpoutEntity) e)) { EntitySpawnEvent event = Spout.getEventManager() .callEvent(new EntitySpawnEvent(e, e.getTransform().getPosition())); if (event.isCancelled()) { return; } region.getEntityManager().addEntity((SpoutEntity) e); for (Component component : e.values()) { if (component instanceof EntityComponent) { ((EntityComponent) component).onSpawned(); } } } else { throw new IllegalStateException("Cannot spawn an entity that already has an id!"); } } @Override public Entity createAndSpawnEntity(Point point, EntityPrefab prefab, LoadOption option) { getRegionFromBlock(point, option); Entity e = createEntity(point, prefab); return e; } @Override public Entity createAndSpawnEntity(Point point, Class<? extends Component> type, LoadOption option) { getRegionFromBlock(point, option); Entity e = createEntity(point, type); spawnEntity(e); return e; } @Override public Entity[] createAndSpawnEntity(Point[] points, Class<? extends Component> type, LoadOption option) { Entity[] entities = new Entity[points.length]; for (int i = 0; i < points.length; i++) { entities[i] = createAndSpawnEntity(points[i], type, option); } return entities; } @Override public Entity[] createAndSpawnEntity(SpawnArrangement arrangement, Class<? extends Component> type, LoadOption option) { return createAndSpawnEntity(arrangement.getArrangement(), type, option); } @Override public void copySnapshotRun() throws InterruptedException { snapshotManager.copyAllSnapshots(); } @Override public void startTickRun(int stage, long delta) throws InterruptedException { switch (stage) { case 0: { age.set(age.get() + delta); parallelTaskManager.heartbeat(delta); taskManager.heartbeat(delta); for (Component component : componentHolder.values()) { component.tick(delta); } break; } default: { throw new IllegalStateException("Number of states exceeded limit for SpoutWorld"); } } } @Override public void haltRun() throws InterruptedException { // TODO - save on halt ? } @Override public boolean containsBlock(int x, int y, int z) { return true; } @Override public boolean containsChunk(int x, int y, int z) { return true; } @Override public long getSeed() { return seed; } @Override public SpoutEngine getEngine() { return engine; } /** * Gets the lighting manager that calculates the light for this world * @return world lighting manager */ public SpoutWorldLighting getLightingManager() { return this.lightingManager; } @Override public int getHeight() { // TODO: Variable world height return 256; } @Override public byte getSkyLight() { return this.skyLightLevel; } @Override public void setSkyLight(byte newLight) { this.skyLightLevel = newLight; } @Override public void updateBlockPhysics(int x, int y, int z) { this.getRegionFromBlock(x, y, z).updateBlockPhysics(x, y, z); } @Override public void queueBlockPhysics(int x, int y, int z, EffectRange range) { queueBlockPhysics(x, y, z, range, null); } public void queueBlockPhysics(int x, int y, int z, EffectRange range, BlockMaterial oldMaterial) { this.getRegionFromBlock(x, y, z).queueBlockPhysics(x, y, z, range, oldMaterial); } @Override public Transform getSpawnPoint() { return spawnLocation.copy(); } @Override public void setSpawnPoint(Transform transform) { spawnLocation.set(transform); } @Override public void finalizeRun() throws InterruptedException { synchronized (columnSet) { for (SpoutColumn c : columnSet) { c.onFinalize(); } } } @Override public void preSnapshotRun() throws InterruptedException { } @Override public WorldGenerator getGenerator() { return generator; } @Override public List<Entity> getAll() { ArrayList<Entity> entities = new ArrayList<Entity>(); for (Region region : regions) { entities.addAll(region.getAll()); } return entities; } @Override public Entity getEntity(int id) { for (Region region : regions) { Entity entity = region.getEntity(id); if (entity != null) { return entity; } } return null; } public void addPlayer(Player player) { players.add(player); } public void removePlayer(Player player) { players.remove(player); } @Override public List<Player> getPlayers() { return Collections.unmodifiableList(players); } @Override public List<Entity> getNearbyEntities(Point position, Entity ignore, int range) { ArrayList<Entity> foundEntities = new ArrayList<Entity>(); final int RANGE_SQUARED = range * range; for (Entity entity : getEntitiesNearRegion(position, range)) { if (entity != null && entity != ignore) { double distance = MathHelper.distanceSquared(position, entity.getTransform().getPosition()); if (distance < RANGE_SQUARED) { foundEntities.add(entity); } } } return Collections.unmodifiableList(foundEntities); } @Override public List<Entity> getNearbyEntities(Point position, int range) { return getNearbyEntities(position, null, range); } @Override public List<Entity> getNearbyEntities(Entity entity, int range) { return getNearbyEntities(entity.getTransform().getPosition(), range); } @Override public Entity getNearestEntity(Point position, Entity ignore, int range) { Entity best = null; double bestDistance = range * range; for (Entity entity : getEntitiesNearRegion(position, range)) { if (entity != null && entity != ignore) { double distance = MathHelper.distanceSquared(position, entity.getTransform().getPosition()); if (distance < bestDistance) { bestDistance = distance; best = entity; } } } return best; } @Override public Entity getNearestEntity(Point position, int range) { return getNearestEntity(position, null, range); } @Override public Entity getNearestEntity(Entity entity, int range) { return getNearestEntity(entity.getTransform().getPosition(), range); } /** * Gets a set of nearby players to the point, inside of the range. * The search will ignore the specified entity. * @param position of the center * @param ignore Entity to ignore * @param range to look for * @return A set of nearby Players */ @Override @LiveRead @Threadsafe public List<Player> getNearbyPlayers(Point position, Player ignore, int range) { ArrayList<Player> foundPlayers = new ArrayList<Player>(); for (Entity entity : getNearbyEntities(position, ignore, range)) { if (entity instanceof Player) { foundPlayers.add((Player) entity); } } return Collections.unmodifiableList(foundPlayers); } /** * Gets a set of nearby players to the point, inside of the range * @param position of the center * @param range to look for * @return A set of nearby Players */ @Override @LiveRead @Threadsafe public List<Player> getNearbyPlayers(Point position, int range) { return getNearbyPlayers(position, null, range); } /** * Gets a set of nearby players to the entity, inside of the range * @param entity marking the center and which is ignored * @param range to look for * @return A set of nearby Players */ @Override @LiveRead @Threadsafe public List<Player> getNearbyPlayers(Entity entity, int range) { return getNearbyPlayers(entity.getTransform().getPosition(), range); } /** * Gets the absolute closest player from the specified point within a specified range. * @param position to search from * @param ignore to ignore while searching * @param range to search * @return nearest player */ @Override @LiveRead @Threadsafe public Player getNearestPlayer(Point position, Player ignore, int range) { Entity best = null; double bestDistance = range * range; for (Entity entity : getEntitiesNearRegion(position, range)) { if (entity != null && entity instanceof Player && entity != ignore) { double distance = MathHelper.distanceSquared(position, entity.getTransform().getPosition()); if (distance < bestDistance) { bestDistance = distance; best = entity; } } } return (Player) best; } /** * Gets the absolute closest player from the specified point within a specified range. * @param range to search * @return nearest player */ @Override @LiveRead @Threadsafe public Player getNearestPlayer(Point position, int range) { return getNearestPlayer(position, null, range); } /** * Gets the absolute closest player from the specified point within a specified range. * @param entity to search from * @param range to search * @return nearest player */ @Override @LiveRead @Threadsafe public Player getNearestPlayer(Entity entity, int range) { return getNearestPlayer(entity.getTransform().getPosition(), range); } /** * Finds all the players inside of the regions inside the range area * @param position to search from * @param range to search for regions * @return nearby region's players */ private List<Entity> getEntitiesNearRegion(Point position, int range) { Region center = this.getRegionFromBlock(position, LoadOption.NO_LOAD); ArrayList<Entity> entities = new ArrayList<Entity>(); if (center != null) { final int regions = (range + Region.BLOCKS.SIZE - 1) / Region.BLOCKS.SIZE; //round up 1 region size for (int dx = -regions; dx < regions; dx++) { for (int dy = -regions; dy < regions; dy++) { for (int dz = -regions; dz < regions; dz++) { Region region = this.getRegion(center.getX() + dx, center.getY() + dy, center.getZ() + dz, LoadOption.NO_LOAD); if (region != null) { entities.addAll(region.getAll()); } } } } } return entities; } public List<CollisionVolume> getCollidingObject(CollisionModel model) { //TODO Make this more general final int minX = MathHelper.floor(model.getVolume().getPosition().getX()); final int minY = MathHelper.floor(model.getVolume().getPosition().getY()); final int minZ = MathHelper.floor(model.getVolume().getPosition().getZ()); final int maxX = minX + 1; final int maxY = minY + 1; final int maxZ = minZ + 1; final LinkedList<CollisionVolume> colliding = new LinkedList<CollisionVolume>(); final BoundingBox mutable = new BoundingBox(0, 0, 0, 0, 0, 0); for (int dx = minX; dx < maxX; dx++) { for (int dy = minY; dy < maxY; dy++) { for (int dz = minZ; dz < maxZ; dz++) { BlockMaterial material = this.getBlockMaterial(dx, dy, dz); mutable.set((BoundingBox) material.getBoundingArea()); BoundingBox box = mutable.offset(dx, dy, dz); if (box.intersects(model.getVolume())) { colliding.add(mutable.clone()); } } } } //TODO: colliding entities return colliding; } /** * Removes a column corresponding to the given Column coordinates * @param x the x coordinate * @param z the z coordinate */ public void removeColumn(int x, int z, SpoutColumn column) { long key = IntPairHashed.key(x, z); if (columns.remove(key, column)) { synchronized (columnSet) { columnSet.remove(column); } } } /** * Gets the column corresponding to the given Block coordinates * @param x the x block coordinate * @param z the z block coordinate * @param create true to create the column if it doesn't exist * @return the column or null if it doesn't exist */ public SpoutColumn getColumn(int x, int z, boolean create) { int colX = x >> SpoutColumn.BLOCKS.BITS; int colZ = z >> SpoutColumn.BLOCKS.BITS; long key = IntPairHashed.key(colX, colZ); SpoutColumn column = columns.get(key); if (create && column == null) { SpoutColumn newColumn = new SpoutColumn(this, colX, colZ); column = columns.putIfAbsent(key, newColumn); if (column == null) { column = newColumn; synchronized (columnSet) { columnSet.add(column); } } } return column; } public SpoutColumn getColumn(int x, int z) { return getColumn(x, z, false); } private BAAWrapper getColumnHeightMapBAA(int x, int z) { int cx = x >> Region.CHUNKS.BITS; int cz = z >> Region.CHUNKS.BITS; BAAWrapper baa = null; baa = heightMapBAAs.get(cx, cz); if (baa == null) { File columnDirectory = new File(worldDirectory, "col"); columnDirectory.mkdirs(); File file = new File(columnDirectory, "col" + cx + "_" + cz + ".scl"); baa = new BAAWrapper(file, 1024, 256, RegionFileManager.TIMEOUT); BAAWrapper oldBAA = heightMapBAAs.putIfAbsent(cx, cz, baa); if (oldBAA != null) { baa = oldBAA; } } return baa; } public InputStream getHeightMapInputStream(int x, int z) { BAAWrapper baa = getColumnHeightMapBAA(x, z); int key = NibblePairHashed.key(x, z) & 0xFF; return baa.getBlockInputStream(key); } public OutputStream getHeightMapOutputStream(int x, int z) { BAAWrapper baa = getColumnHeightMapBAA(x, z); int key = NibblePairHashed.key(x, z) & 0xFF; return baa.getBlockOutputStream(key); } @Override public Entity getEntity(UUID uid) { for (Region region : regions) { for (Entity e : region.getAll()) { if (e.getUID().equals(uid)) { return e; } } } return null; } @Override public String toString() { return toString(this.getName(), this.getUID(), this.getAge()); } private static String toString(String name, UUID uid, long age) { return "SpoutWorld{ " + name + " UUID: " + uid + " Age: " + age + "}"; } @Override public File getDirectory() { return worldDirectory; } public void unload(boolean save) { for (Component component : componentHolder.values()) { component.onDetached(); } this.getLightingManager().abort(); if (save) { new WorldData(this).saveToFile(); } Collection<Region> regions = this.regions.getRegions(); final int total = Math.max(1, regions.size()); int progress = 0; for (Region r : regions) { r.unload(save); progress++; if (save && progress % 4 == 0) { Spout.getLogger().info( "Saving world [" + getName() + "], " + (int) (progress * 100F / total) + "% Complete"); } } } public Collection<Region> getRegions() { return this.regions.getRegions(); } @Override public boolean hasChunk(int x, int y, int z) { return this.getChunk(x, y, z, LoadOption.NO_LOAD) != null; } @Override public boolean hasChunkAtBlock(int x, int y, int z) { return this.getChunkFromBlock(x, y, z, LoadOption.NO_LOAD) != null; } @Override public void saveChunk(int x, int y, int z) { SpoutRegion r = this.getRegionFromChunk(x, y, z, LoadOption.NO_LOAD); if (r != null) { r.saveChunk(x, y, z); } } @Override public void unloadChunk(int x, int y, int z, boolean save) { SpoutRegion r = this.getRegionFromChunk(x, y, z, LoadOption.NO_LOAD); if (r != null) { r.unloadChunk(x, y, z, save); } } @Override public int getNumLoadedChunks() { int total = 0; for (Region region : this.regions) { total += region.getNumLoadedChunks(); } return total; } @Override public boolean setBlockLight(int x, int y, int z, byte light, Cause<?> cause) { return this.getChunkFromBlock(x, y, z).setBlockLight(x, y, z, light, cause); } @Override public boolean setBlockSkyLight(int x, int y, int z, byte light, Cause<?> cause) { return this.getChunkFromBlock(x, y, z).setBlockSkyLight(x, y, z, light, cause); } @Override public BlockComponent getBlockComponent(int x, int y, int z) { return this.getRegionFromBlock(x, y, z).getBlockComponent(x, y, z); } @Override public TaskManager getParallelTaskManager() { return parallelTaskManager; } @Override public TaskManager getTaskManager() { return taskManager; } private SpoutChunk[][][] getChunks(int x, int y, int z, CuboidBlockMaterialBuffer buffer) { Vector3 size = buffer.getSize(); int startX = x; int startY = y; int startZ = z; int endX = x + size.getFloorX(); int endY = y + size.getFloorY(); int endZ = z + size.getFloorZ(); Chunk start = getChunkFromBlock(startX, startY, startZ); Chunk end = getChunkFromBlock(endX - 1, endY - 1, endZ - 1); int chunkStartX = start.getX(); int chunkStartY = start.getY(); int chunkStartZ = start.getZ(); int chunkEndX = end.getX(); int chunkEndY = end.getY(); int chunkEndZ = end.getZ(); int chunkSizeX = chunkEndX - chunkStartX + 1; int chunkSizeY = chunkEndY - chunkStartY + 1; int chunkSizeZ = chunkEndZ - chunkStartZ + 1; SpoutChunk[][][] chunks = new SpoutChunk[chunkSizeX][chunkSizeY][chunkSizeZ]; for (int dx = chunkStartX; dx <= chunkEndX; dx++) { for (int dy = chunkStartY; dy <= chunkEndY; dy++) { for (int dz = chunkStartZ; dz <= chunkEndZ; dz++) { SpoutChunk chunk = getChunk(dx, dy, dz, LoadOption.LOAD_GEN); if (chunk == null) { throw new IllegalStateException("Null chunk loaded with LoadOption.LOAD_GEN"); } chunks[dx - chunkStartX][dy - chunkStartY][dz - chunkStartZ] = chunk; } } } return chunks; } protected void lockChunks(SpoutChunk[][][] chunks) { for (int dx = 0; dx < chunks.length; dx++) { SpoutChunk[][] subArray1 = chunks[dx]; for (int dy = 0; dy < subArray1.length; dy++) { SpoutChunk[] subArray2 = subArray1[dy]; for (int dz = 0; dz < subArray2.length; dz++) { subArray2[dz].lockStore(); } } } } protected void unlockChunks(SpoutChunk[][][] chunks) { for (int dx = 0; dx < chunks.length; dx++) { SpoutChunk[][] subArray1 = chunks[dx]; for (int dy = 0; dy < subArray1.length; dy++) { SpoutChunk[] subArray2 = subArray1[dy]; for (int dz = 0; dz < subArray2.length; dz++) { subArray2[dz].unlockStore(); } } } } @Override public void setCuboid(CuboidBlockMaterialBuffer buffer, Cause<?> cause) { Vector3 base = buffer.getBase(); setCuboid(base.getFloorX(), base.getFloorY(), base.getFloorZ(), buffer, cause); } @Override public void setCuboid(int x, int y, int z, CuboidBlockMaterialBuffer buffer, Cause<?> cause) { if (cause == null) { throw new NullPointerException("Cause can not be null"); } CuboidChangeEvent event = new CuboidChangeEvent(buffer, cause); Spout.getEngine().getEventManager().callEvent(event); if (event.isCancelled()) { return; } SpoutChunk[][][] chunks = getChunks(x, y, z, buffer); setCuboid(chunks, x, y, z, buffer, cause); } protected void setCuboid(SpoutChunk[][][] chunks, int x, int y, int z, CuboidBlockMaterialBuffer buffer, Cause<?> cause) { lockChunks(chunks); try { for (int dx = 0; dx < chunks.length; dx++) { SpoutChunk[][] subArray1 = chunks[dx]; for (int dy = 0; dy < subArray1.length; dy++) { SpoutChunk[] subArray2 = subArray1[dy]; for (int dz = 0; dz < subArray2.length; dz++) { subArray2[dz].setCuboid(x, y, z, buffer, cause); } } } } finally { unlockChunks(chunks); } } @Override public CuboidBlockMaterialBuffer getCuboid(int x, int y, int z, int sx, int sy, int sz) { CuboidBlockMaterialBuffer buffer = new CuboidBlockMaterialBuffer(x, y, z, sx, sy, sz); getCuboid(x, y, z, buffer); return buffer; } @Override public void getCuboid(CuboidBlockMaterialBuffer buffer) { Vector3 base = buffer.getBase(); getCuboid(base.getFloorX(), base.getFloorY(), base.getFloorZ(), buffer); } @Override public void getCuboid(int x, int y, int z, CuboidBlockMaterialBuffer buffer) { SpoutChunk[][][] chunks = getChunks(x, y, z, buffer); getCuboid(chunks, x, y, z, buffer); } protected void getCuboid(SpoutChunk[][][] chunks, int x, int y, int z, CuboidBlockMaterialBuffer buffer) { lockChunks(chunks); try { for (int dx = 0; dx < chunks.length; dx++) { SpoutChunk[][] subArray1 = chunks[dx]; for (int dy = 0; dy < subArray1.length; dy++) { SpoutChunk[] subArray2 = subArray1[dy]; for (int dz = 0; dz < subArray2.length; dz++) { subArray2[dz].getCuboid(x, y, z, buffer); } } } } finally { unlockChunks(chunks); } } public void enableRenderQueue() { this.renderQueueEnabled.set(true); } public void disableRenderQueue() { this.renderQueueEnabled.set(false); } public boolean isRenderQueueEnabled() { return renderQueueEnabled.get(); } // Worlds don't do any of these @Override public void runPhysics(int sequence) throws InterruptedException { } @Override public void runLighting(int sequence) throws InterruptedException { } @Override public long getFirstDynamicUpdateTime() { return SpoutScheduler.END_OF_THE_WORLD; } @Override public void runDynamicUpdates(long time, int sequence) throws InterruptedException { } public WeakReference<World> getWeakReference() { return selfReference; } @Override public ComponentHolder getComponentHolder() { return componentHolder; } @Override public DefaultedMap<String, Serializable> getDataMap() { return componentHolder.getData(); } public RegionFileManager getRegionFileManager() { return regionFileManager; } public BAAWrapper getRegionFile(int rx, int ry, int rz) { return regionFileManager.getBAAWrapper(rx, ry, rz); } public OutputStream getChunkOutputStream(ChunkSnapshot c) { return regionFileManager.getChunkOutputStream(c); } public Model getSkydomeModel() { return skydome; } public void setSkydomeModel(Model model) { this.skydome = model; } }