Entities are now transferred from server to client
- Entities are no longer internally bound to chunks - Entity visibility for a player is now unique for each entity - Entities are send/revoked just like chunks by EntityManager - LocalPlayer no longer stores an entity; instead it stores entity ID and looks up the entity on demand
This commit is contained in:
		@@ -1,8 +1,11 @@
 | 
				
			|||||||
package ru.windcorp.progressia.client;
 | 
					package ru.windcorp.progressia.client;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.apache.logging.log4j.LogManager;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import ru.windcorp.progressia.client.comms.DefaultClientCommsListener;
 | 
					import ru.windcorp.progressia.client.comms.DefaultClientCommsListener;
 | 
				
			||||||
import ru.windcorp.progressia.client.comms.ServerCommsChannel;
 | 
					import ru.windcorp.progressia.client.comms.ServerCommsChannel;
 | 
				
			||||||
import ru.windcorp.progressia.client.graphics.world.Camera;
 | 
					import ru.windcorp.progressia.client.graphics.world.Camera;
 | 
				
			||||||
 | 
					import ru.windcorp.progressia.client.graphics.world.EntityAnchor;
 | 
				
			||||||
import ru.windcorp.progressia.client.graphics.world.LocalPlayer;
 | 
					import ru.windcorp.progressia.client.graphics.world.LocalPlayer;
 | 
				
			||||||
import ru.windcorp.progressia.client.world.WorldRender;
 | 
					import ru.windcorp.progressia.client.world.WorldRender;
 | 
				
			||||||
import ru.windcorp.progressia.common.world.WorldData;
 | 
					import ru.windcorp.progressia.common.world.WorldData;
 | 
				
			||||||
@@ -11,7 +14,7 @@ import ru.windcorp.progressia.common.world.entity.EntityData;
 | 
				
			|||||||
public class Client {
 | 
					public class Client {
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	private final WorldRender world;
 | 
						private final WorldRender world;
 | 
				
			||||||
	private LocalPlayer localPlayer;
 | 
						private final LocalPlayer localPlayer = new LocalPlayer(this);
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	private final Camera camera = new Camera((float) Math.toRadians(70));
 | 
						private final Camera camera = new Camera((float) Math.toRadians(70));
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
@@ -32,8 +35,8 @@ public class Client {
 | 
				
			|||||||
		return localPlayer;
 | 
							return localPlayer;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	public void setLocalPlayer(EntityData localPlayer) {
 | 
						public boolean isReady() {
 | 
				
			||||||
		this.localPlayer = new LocalPlayer(localPlayer);
 | 
							return localPlayer.hasEntity();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	public Camera getCamera() {
 | 
						public Camera getCamera() {
 | 
				
			||||||
@@ -44,4 +47,17 @@ public class Client {
 | 
				
			|||||||
		return comms;
 | 
							return comms;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public void onLocalPlayerEntityChanged(EntityData entity, EntityData lastKnownEntity) {
 | 
				
			||||||
 | 
							LogManager.getLogger().info("LocalPlayer entity changed from {} to {}", lastKnownEntity, entity);
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							if (entity == null) {
 | 
				
			||||||
 | 
								getCamera().setAnchor(null);
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							getCamera().setAnchor(new EntityAnchor(
 | 
				
			||||||
 | 
									getWorld().getEntityRenderable(entity)
 | 
				
			||||||
 | 
							));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,15 +2,12 @@ package ru.windcorp.progressia.client.comms;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import java.io.IOException;
 | 
					import java.io.IOException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import ru.windcorp.jputil.chars.StringUtil;
 | 
					 | 
				
			||||||
import ru.windcorp.progressia.client.Client;
 | 
					import ru.windcorp.progressia.client.Client;
 | 
				
			||||||
import ru.windcorp.progressia.client.graphics.world.EntityAnchor;
 | 
					 | 
				
			||||||
import ru.windcorp.progressia.common.comms.CommsListener;
 | 
					import ru.windcorp.progressia.common.comms.CommsListener;
 | 
				
			||||||
import ru.windcorp.progressia.common.comms.packets.Packet;
 | 
					import ru.windcorp.progressia.common.comms.packets.Packet;
 | 
				
			||||||
import ru.windcorp.progressia.common.util.crash.CrashReports;
 | 
					import ru.windcorp.progressia.common.util.crash.CrashReports;
 | 
				
			||||||
import ru.windcorp.progressia.common.world.PacketSetLocalPlayer;
 | 
					import ru.windcorp.progressia.common.world.PacketSetLocalPlayer;
 | 
				
			||||||
import ru.windcorp.progressia.common.world.PacketWorldChange;
 | 
					import ru.windcorp.progressia.common.world.PacketWorldChange;
 | 
				
			||||||
import ru.windcorp.progressia.common.world.entity.EntityData;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO refactor with no mercy
 | 
					// TODO refactor with no mercy
 | 
				
			||||||
public class DefaultClientCommsListener implements CommsListener {
 | 
					public class DefaultClientCommsListener implements CommsListener {
 | 
				
			||||||
@@ -33,26 +30,12 @@ public class DefaultClientCommsListener implements CommsListener {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private void setLocalPlayer(PacketSetLocalPlayer packet) {
 | 
						private void setLocalPlayer(PacketSetLocalPlayer packet) {
 | 
				
			||||||
		EntityData entity = getClient().getWorld().getData().getEntity(
 | 
							getClient().getLocalPlayer().setEntityId(packet.getEntityId());
 | 
				
			||||||
				packet.getEntityId()
 | 
					 | 
				
			||||||
		);
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		if (entity == null) {
 | 
					 | 
				
			||||||
			CrashReports.report(
 | 
					 | 
				
			||||||
					null,
 | 
					 | 
				
			||||||
					"Player entity with ID %s not found",
 | 
					 | 
				
			||||||
					new String(StringUtil.toFullHex(packet.getEntityId()))
 | 
					 | 
				
			||||||
			);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		getClient().setLocalPlayer(entity);
 | 
					 | 
				
			||||||
		getClient().getCamera().setAnchor(new EntityAnchor(
 | 
					 | 
				
			||||||
				getClient().getWorld().getEntityRenderable(entity)
 | 
					 | 
				
			||||||
		));
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public void onIOError(IOException reason) {
 | 
						public void onIOError(IOException reason) {
 | 
				
			||||||
 | 
							CrashReports.report(reason, "An IOException has occurred in communications");
 | 
				
			||||||
		// TODO implement
 | 
							// TODO implement
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -75,7 +75,9 @@ public class LayerWorld extends Layer {
 | 
				
			|||||||
			renderWorld();
 | 
								renderWorld();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		if (client.getLocalPlayer() != null) {
 | 
							client.getLocalPlayer().getEntity();
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							if (client.isReady()) {
 | 
				
			||||||
			client.getLocalPlayer().update(client.getWorld());
 | 
								client.getLocalPlayer().update(client.getWorld());
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -139,10 +141,9 @@ public class LayerWorld extends Layer {
 | 
				
			|||||||
	private static final Renderable SELECTION_BOX = tmp_createSelectionBox();
 | 
						private static final Renderable SELECTION_BOX = tmp_createSelectionBox();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private void tmp_drawSelectionBox() {
 | 
						private void tmp_drawSelectionBox() {
 | 
				
			||||||
		LocalPlayer player = client.getLocalPlayer();
 | 
							if (!client.isReady()) return;
 | 
				
			||||||
		if (player == null) return;
 | 
					 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		Vec3i selection = player.getSelection().getBlock();
 | 
							Vec3i selection = client.getLocalPlayer().getSelection().getBlock();
 | 
				
			||||||
		if (selection == null) return;
 | 
							if (selection == null) return;
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		helper.pushTransform().translate(selection.x, selection.y, selection.z);
 | 
							helper.pushTransform().translate(selection.x, selection.y, selection.z);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,16 +1,59 @@
 | 
				
			|||||||
package ru.windcorp.progressia.client.graphics.world;
 | 
					package ru.windcorp.progressia.client.graphics.world;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import ru.windcorp.progressia.client.Client;
 | 
				
			||||||
import ru.windcorp.progressia.client.world.WorldRender;
 | 
					import ru.windcorp.progressia.client.world.WorldRender;
 | 
				
			||||||
import ru.windcorp.progressia.client.world.entity.EntityRenderable;
 | 
					import ru.windcorp.progressia.client.world.entity.EntityRenderable;
 | 
				
			||||||
import ru.windcorp.progressia.common.world.PlayerData;
 | 
					 | 
				
			||||||
import ru.windcorp.progressia.common.world.entity.EntityData;
 | 
					import ru.windcorp.progressia.common.world.entity.EntityData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class LocalPlayer extends PlayerData {
 | 
					public class LocalPlayer {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private final Client client;
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						private long entityId = EntityData.NULL_ENTITY_ID;
 | 
				
			||||||
 | 
						private EntityData lastKnownEntity = null;
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	private final Selection selection = new Selection();
 | 
						private final Selection selection = new Selection();
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	public LocalPlayer(EntityData entity) {
 | 
						public LocalPlayer(Client client) {
 | 
				
			||||||
		super(entity);
 | 
							this.client = client;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						public Client getClient() {
 | 
				
			||||||
 | 
							return client;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						public long getEntityId() {
 | 
				
			||||||
 | 
							return entityId;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						public void setEntityId(long entityId) {
 | 
				
			||||||
 | 
							this.entityId = entityId;
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
							this.lastKnownEntity = null;
 | 
				
			||||||
 | 
							getEntity();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						public boolean hasEntityId() {
 | 
				
			||||||
 | 
							return entityId != EntityData.NULL_ENTITY_ID;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						public boolean hasEntity() {
 | 
				
			||||||
 | 
							return getEntity() != null;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						public EntityData getEntity() {
 | 
				
			||||||
 | 
							if (!hasEntityId()) {
 | 
				
			||||||
 | 
								return null;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							EntityData entity = getClient().getWorld().getData().getEntity(getEntityId());
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							if (entity != lastKnownEntity) {
 | 
				
			||||||
 | 
								getClient().onLocalPlayerEntityChanged(entity, lastKnownEntity);
 | 
				
			||||||
 | 
								this.lastKnownEntity = entity;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							return entity;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	public Selection getSelection() {
 | 
						public Selection getSelection() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -116,6 +116,8 @@ public abstract class NPedModel extends EntityRenderable {
 | 
				
			|||||||
		this.body = body;
 | 
							this.body = body;
 | 
				
			||||||
		this.head = head;
 | 
							this.head = head;
 | 
				
			||||||
		this.scale = scale;
 | 
							this.scale = scale;
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							evaluateAngles();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,7 +23,6 @@ import java.util.ArrayList;
 | 
				
			|||||||
import java.util.Arrays;
 | 
					import java.util.Arrays;
 | 
				
			||||||
import java.util.Collection;
 | 
					import java.util.Collection;
 | 
				
			||||||
import java.util.Collections;
 | 
					import java.util.Collections;
 | 
				
			||||||
import java.util.List;
 | 
					 | 
				
			||||||
import java.util.Objects;
 | 
					import java.util.Objects;
 | 
				
			||||||
import java.util.function.BiConsumer;
 | 
					import java.util.function.BiConsumer;
 | 
				
			||||||
import java.util.function.Consumer;
 | 
					import java.util.function.Consumer;
 | 
				
			||||||
@@ -32,7 +31,6 @@ import glm.vec._3.i.Vec3i;
 | 
				
			|||||||
import ru.windcorp.progressia.common.util.VectorUtil;
 | 
					import ru.windcorp.progressia.common.util.VectorUtil;
 | 
				
			||||||
import ru.windcorp.progressia.common.world.block.BlockData;
 | 
					import ru.windcorp.progressia.common.world.block.BlockData;
 | 
				
			||||||
import ru.windcorp.progressia.common.world.block.BlockFace;
 | 
					import ru.windcorp.progressia.common.world.block.BlockFace;
 | 
				
			||||||
import ru.windcorp.progressia.common.world.entity.EntityData;
 | 
					 | 
				
			||||||
import ru.windcorp.progressia.common.world.generic.GenericChunk;
 | 
					import ru.windcorp.progressia.common.world.generic.GenericChunk;
 | 
				
			||||||
import ru.windcorp.progressia.common.world.tile.TileData;
 | 
					import ru.windcorp.progressia.common.world.tile.TileData;
 | 
				
			||||||
import ru.windcorp.progressia.common.world.tile.TileDataStack;
 | 
					import ru.windcorp.progressia.common.world.tile.TileDataStack;
 | 
				
			||||||
@@ -61,9 +59,6 @@ implements GenericChunk<
 | 
				
			|||||||
		BLOCK_FACE_COUNT
 | 
							BLOCK_FACE_COUNT
 | 
				
			||||||
	];
 | 
						];
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	private final List<EntityData> entities =
 | 
					 | 
				
			||||||
			Collections.synchronizedList(new ArrayList<>());
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	private final Collection<ChunkDataListener> listeners =
 | 
						private final Collection<ChunkDataListener> listeners =
 | 
				
			||||||
			Collections.synchronizedCollection(new ArrayList<>());
 | 
								Collections.synchronizedCollection(new ArrayList<>());
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
@@ -160,10 +155,6 @@ implements GenericChunk<
 | 
				
			|||||||
				face.getId();
 | 
									face.getId();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	public List<EntityData> getEntities() {
 | 
					 | 
				
			||||||
		return entities;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	private static void checkLocalCoordinates(Vec3i posInChunk) {
 | 
						private static void checkLocalCoordinates(Vec3i posInChunk) {
 | 
				
			||||||
		if (!isInBounds(posInChunk)) {
 | 
							if (!isInBounds(posInChunk)) {
 | 
				
			||||||
			throw new IllegalCoordinatesException(
 | 
								throw new IllegalCoordinatesException(
 | 
				
			||||||
@@ -219,10 +210,6 @@ implements GenericChunk<
 | 
				
			|||||||
		forEachTileStack(stack -> stack.forEach(tileData -> action.accept(stack, tileData)));
 | 
							forEachTileStack(stack -> stack.forEach(tileData -> action.accept(stack, tileData)));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	public void forEachEntity(Consumer<EntityData> action) {
 | 
					 | 
				
			||||||
		getEntities().forEach(action);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	public WorldData getWorld() {
 | 
						public WorldData getWorld() {
 | 
				
			||||||
		return world;
 | 
							return world;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,11 +20,12 @@ package ru.windcorp.progressia.common.world;
 | 
				
			|||||||
import java.util.ArrayList;
 | 
					import java.util.ArrayList;
 | 
				
			||||||
import java.util.Collection;
 | 
					import java.util.Collection;
 | 
				
			||||||
import java.util.Collections;
 | 
					import java.util.Collections;
 | 
				
			||||||
 | 
					import java.util.Objects;
 | 
				
			||||||
import glm.vec._3.i.Vec3i;
 | 
					import glm.vec._3.i.Vec3i;
 | 
				
			||||||
import gnu.trove.impl.sync.TSynchronizedLongObjectMap;
 | 
					import gnu.trove.TCollections;
 | 
				
			||||||
import gnu.trove.map.TLongObjectMap;
 | 
					import gnu.trove.map.TLongObjectMap;
 | 
				
			||||||
import gnu.trove.map.hash.TLongObjectHashMap;
 | 
					import gnu.trove.map.hash.TLongObjectHashMap;
 | 
				
			||||||
 | 
					import gnu.trove.set.TLongSet;
 | 
				
			||||||
import ru.windcorp.progressia.common.collision.CollisionModel;
 | 
					import ru.windcorp.progressia.common.collision.CollisionModel;
 | 
				
			||||||
import ru.windcorp.progressia.common.world.block.BlockData;
 | 
					import ru.windcorp.progressia.common.world.block.BlockData;
 | 
				
			||||||
import ru.windcorp.progressia.common.world.entity.EntityData;
 | 
					import ru.windcorp.progressia.common.world.entity.EntityData;
 | 
				
			||||||
@@ -46,14 +47,14 @@ implements GenericWorld<
 | 
				
			|||||||
> {
 | 
					> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private final ChunkMap<ChunkData> chunksByPos = new LongBasedChunkMap<>(
 | 
						private final ChunkMap<ChunkData> chunksByPos = new LongBasedChunkMap<>(
 | 
				
			||||||
			new TSynchronizedLongObjectMap<>(new TLongObjectHashMap<>(), this)
 | 
								TCollections.synchronizedMap(new TLongObjectHashMap<>())
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	private final Collection<ChunkData> chunks =
 | 
						private final Collection<ChunkData> chunks =
 | 
				
			||||||
			Collections.unmodifiableCollection(chunksByPos.values());
 | 
								Collections.unmodifiableCollection(chunksByPos.values());
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	private final TLongObjectMap<EntityData> entitiesById =
 | 
						private final TLongObjectMap<EntityData> entitiesById =
 | 
				
			||||||
			new TSynchronizedLongObjectMap<>(new TLongObjectHashMap<>(), this);
 | 
								TCollections.synchronizedMap(new TLongObjectHashMap<>());
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	private final Collection<EntityData> entities =
 | 
						private final Collection<EntityData> entities =
 | 
				
			||||||
			Collections.unmodifiableCollection(entitiesById.valueCollection());
 | 
								Collections.unmodifiableCollection(entitiesById.valueCollection());
 | 
				
			||||||
@@ -86,6 +87,10 @@ implements GenericWorld<
 | 
				
			|||||||
		return entities;
 | 
							return entities;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
 | 
						public TLongSet getLoadedEntities() {
 | 
				
			||||||
 | 
							return entitiesById.keySet();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
	public void tmp_generate() {
 | 
						public void tmp_generate() {
 | 
				
			||||||
		final int size = 1;
 | 
							final int size = 1;
 | 
				
			||||||
		Vec3i cursor = new Vec3i(0, 0, 0);
 | 
							Vec3i cursor = new Vec3i(0, 0, 0);
 | 
				
			||||||
@@ -118,10 +123,6 @@ implements GenericWorld<
 | 
				
			|||||||
		
 | 
							
 | 
				
			||||||
		chunksByPos.put(chunk, chunk);
 | 
							chunksByPos.put(chunk, chunk);
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		chunk.forEachEntity(entity ->
 | 
					 | 
				
			||||||
			entitiesById.put(entity.getEntityId(), entity)
 | 
					 | 
				
			||||||
		);
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		chunk.onLoaded();
 | 
							chunk.onLoaded();
 | 
				
			||||||
		getListeners().forEach(l -> l.onChunkLoaded(this, chunk));
 | 
							getListeners().forEach(l -> l.onChunkLoaded(this, chunk));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -130,10 +131,6 @@ implements GenericWorld<
 | 
				
			|||||||
		getListeners().forEach(l -> l.beforeChunkUnloaded(this, chunk));
 | 
							getListeners().forEach(l -> l.beforeChunkUnloaded(this, chunk));
 | 
				
			||||||
		chunk.beforeUnloaded();
 | 
							chunk.beforeUnloaded();
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		chunk.forEachEntity(entity ->
 | 
					 | 
				
			||||||
			entitiesById.remove(entity.getEntityId())
 | 
					 | 
				
			||||||
		);
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		chunksByPos.remove(chunk);
 | 
							chunksByPos.remove(chunk);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
@@ -153,6 +150,45 @@ implements GenericWorld<
 | 
				
			|||||||
		return entitiesById.get(entityId);
 | 
							return entitiesById.get(entityId);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
 | 
						public void addEntity(EntityData entity) {
 | 
				
			||||||
 | 
							Objects.requireNonNull(entity, "entity");
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							EntityData previous = entitiesById.putIfAbsent(entity.getEntityId(), entity);
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							if (previous != null) {
 | 
				
			||||||
 | 
								String message = "Cannot add entity " + entity + ": ";
 | 
				
			||||||
 | 
								
 | 
				
			||||||
 | 
								if (previous == entity) {
 | 
				
			||||||
 | 
									message += "already present";
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									message += "entity with the same EntityID already present (" + previous + ")";
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								
 | 
				
			||||||
 | 
								throw new IllegalStateException(message);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							getListeners().forEach(l -> l.onEntityAdded(this, entity));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						public void removeEntity(long entityId) {
 | 
				
			||||||
 | 
							synchronized (entitiesById) {
 | 
				
			||||||
 | 
								EntityData entity = entitiesById.get(entityId);
 | 
				
			||||||
 | 
								
 | 
				
			||||||
 | 
								if (entity == null) {
 | 
				
			||||||
 | 
									throw new IllegalArgumentException("Entity with EntityID " + EntityData.formatEntityId(entityId) + " not present");
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									removeEntity(entity);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						public void removeEntity(EntityData entity) {
 | 
				
			||||||
 | 
							Objects.requireNonNull(entity, "entity");
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							getListeners().forEach(l -> l.beforeEntityRemoved(this, entity));
 | 
				
			||||||
 | 
							entitiesById.remove(entity.getEntityId());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
	public float getTime() {
 | 
						public float getTime() {
 | 
				
			||||||
		return time;
 | 
							return time;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,7 @@ package ru.windcorp.progressia.common.world;
 | 
				
			|||||||
import java.util.function.Consumer;
 | 
					import java.util.function.Consumer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import glm.vec._3.i.Vec3i;
 | 
					import glm.vec._3.i.Vec3i;
 | 
				
			||||||
 | 
					import ru.windcorp.progressia.common.world.entity.EntityData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public interface WorldDataListener {
 | 
					public interface WorldDataListener {
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
@@ -31,4 +32,18 @@ public interface WorldDataListener {
 | 
				
			|||||||
	 */
 | 
						 */
 | 
				
			||||||
	default void beforeChunkUnloaded(WorldData world, ChunkData chunk) {}
 | 
						default void beforeChunkUnloaded(WorldData world, ChunkData chunk) {}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Invoked whenever an {@link EntityData} has been added.
 | 
				
			||||||
 | 
						 * @param world the world instance
 | 
				
			||||||
 | 
						 * @param entity the entity that has been added
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						default void onEntityAdded(WorldData world, EntityData entity) {}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Invoked whenever an {@link EntityData} is about to be removed.
 | 
				
			||||||
 | 
						 * @param world the world instance
 | 
				
			||||||
 | 
						 * @param entity the entity that is going to be removed
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						default void beforeEntityRemoved(WorldData world, EntityData entity) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@ package ru.windcorp.progressia.common.world.entity;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import glm.vec._2.Vec2;
 | 
					import glm.vec._2.Vec2;
 | 
				
			||||||
import glm.vec._3.Vec3;
 | 
					import glm.vec._3.Vec3;
 | 
				
			||||||
 | 
					import ru.windcorp.jputil.chars.StringUtil;
 | 
				
			||||||
import ru.windcorp.progressia.common.collision.Collideable;
 | 
					import ru.windcorp.progressia.common.collision.Collideable;
 | 
				
			||||||
import ru.windcorp.progressia.common.collision.CollisionModel;
 | 
					import ru.windcorp.progressia.common.collision.CollisionModel;
 | 
				
			||||||
import ru.windcorp.progressia.common.state.StatefulObject;
 | 
					import ru.windcorp.progressia.common.state.StatefulObject;
 | 
				
			||||||
@@ -14,6 +15,12 @@ public class EntityData extends StatefulObject implements Collideable, GenericEn
 | 
				
			|||||||
	
 | 
						
 | 
				
			||||||
	private final Vec2 direction = new Vec2();
 | 
						private final Vec2 direction = new Vec2();
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * The unique {@code long} value guaranteed to never be assigned to an entity as its entity ID.
 | 
				
			||||||
 | 
						 * This can safely be used as a placeholder or a sentinel value.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public static final long NULL_ENTITY_ID = 0x0000_0000_0000_0000;
 | 
				
			||||||
 | 
						
 | 
				
			||||||
	private long entityId;
 | 
						private long entityId;
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	private CollisionModel collisionModel = null;
 | 
						private CollisionModel collisionModel = null;
 | 
				
			||||||
@@ -69,6 +76,9 @@ public class EntityData extends StatefulObject implements Collideable, GenericEn
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	public void setEntityId(long entityId) {
 | 
						public void setEntityId(long entityId) {
 | 
				
			||||||
 | 
							if (entityId == NULL_ENTITY_ID) {
 | 
				
			||||||
 | 
								throw new IllegalArgumentException("Attempted to set entity ID to NULL_ENTITY_ID (" + entityId + ")");
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		this.entityId = entityId;
 | 
							this.entityId = entityId;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
@@ -128,4 +138,17 @@ public class EntityData extends StatefulObject implements Collideable, GenericEn
 | 
				
			|||||||
		return output;
 | 
							return output;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public String toString() {
 | 
				
			||||||
 | 
							return new StringBuilder(super.toString())
 | 
				
			||||||
 | 
									.append(" (EntityID ")
 | 
				
			||||||
 | 
									.append(StringUtil.toFullHex(getEntityId()))
 | 
				
			||||||
 | 
									.append(")")
 | 
				
			||||||
 | 
									.toString();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						public static String formatEntityId(long entityId) {
 | 
				
			||||||
 | 
							return new String(StringUtil.toFullHex(entityId));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					package ru.windcorp.progressia.common.world.entity;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.io.DataInput;
 | 
				
			||||||
 | 
					import java.io.DataOutput;
 | 
				
			||||||
 | 
					import java.io.IOException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import ru.windcorp.progressia.common.world.PacketWorldChange;
 | 
				
			||||||
 | 
					import ru.windcorp.progressia.common.world.WorldData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class PacketRevokeEntity extends PacketWorldChange {
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						private long entityId;
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						public PacketRevokeEntity() {
 | 
				
			||||||
 | 
							this("Core:RevokeEntity");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protected PacketRevokeEntity(String id) {
 | 
				
			||||||
 | 
							super(id);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						public void set(long entityId) {
 | 
				
			||||||
 | 
							this.entityId = entityId;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void read(DataInput input) throws IOException {
 | 
				
			||||||
 | 
							this.entityId = input.readLong();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void write(DataOutput output) throws IOException {
 | 
				
			||||||
 | 
							output.writeLong(this.entityId);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void apply(WorldData world) {
 | 
				
			||||||
 | 
							world.removeEntity(this.entityId);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,5 +1,9 @@
 | 
				
			|||||||
package ru.windcorp.progressia.common.world.generic;
 | 
					package ru.windcorp.progressia.common.world.generic;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.Iterator;
 | 
				
			||||||
 | 
					import java.util.NoSuchElementException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import glm.vec._3.i.Vec3i;
 | 
				
			||||||
import gnu.trove.impl.sync.TSynchronizedLongSet;
 | 
					import gnu.trove.impl.sync.TSynchronizedLongSet;
 | 
				
			||||||
import gnu.trove.set.hash.TLongHashSet;
 | 
					import gnu.trove.set.hash.TLongHashSet;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -17,6 +21,78 @@ public class ChunkSets {
 | 
				
			|||||||
		return new LongBasedChunkSet(new TSynchronizedLongSet(new TLongHashSet()));
 | 
							return new LongBasedChunkSet(new TSynchronizedLongSet(new TLongHashSet()));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
 | 
						private final static ChunkSet EMPTY_SET = new ChunkSet() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Override
 | 
				
			||||||
 | 
							public Iterator<Vec3i> iterator() {
 | 
				
			||||||
 | 
								return new Iterator<Vec3i>() {
 | 
				
			||||||
 | 
									@Override
 | 
				
			||||||
 | 
									public boolean hasNext() {
 | 
				
			||||||
 | 
										return false;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									@Override
 | 
				
			||||||
 | 
									public Vec3i next() {
 | 
				
			||||||
 | 
										throw new NoSuchElementException();
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Override
 | 
				
			||||||
 | 
							public int size() {
 | 
				
			||||||
 | 
								return 0;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Override
 | 
				
			||||||
 | 
							public boolean contains(Vec3i pos) {
 | 
				
			||||||
 | 
								return false;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Override
 | 
				
			||||||
 | 
							public boolean add(Vec3i pos) {
 | 
				
			||||||
 | 
								throw new UnsupportedOperationException();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Override
 | 
				
			||||||
 | 
							public boolean remove(Vec3i pos) {
 | 
				
			||||||
 | 
								throw new UnsupportedOperationException();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Override
 | 
				
			||||||
 | 
							public boolean containsAll(ChunkSet other) {
 | 
				
			||||||
 | 
								return false;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Override
 | 
				
			||||||
 | 
							public boolean containsAny(ChunkSet other) {
 | 
				
			||||||
 | 
								return false;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Override
 | 
				
			||||||
 | 
							public void addAll(ChunkSet other) {
 | 
				
			||||||
 | 
								throw new UnsupportedOperationException();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Override
 | 
				
			||||||
 | 
							public void removeAll(ChunkSet other) {
 | 
				
			||||||
 | 
								throw new UnsupportedOperationException();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Override
 | 
				
			||||||
 | 
							public void retainAll(ChunkSet other) {
 | 
				
			||||||
 | 
								throw new UnsupportedOperationException();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Override
 | 
				
			||||||
 | 
							public void clear() {
 | 
				
			||||||
 | 
								throw new UnsupportedOperationException();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						public static ChunkSet empty() {
 | 
				
			||||||
 | 
							return EMPTY_SET;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
	private ChunkSets() {}
 | 
						private ChunkSets() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,10 +8,11 @@ import org.apache.logging.log4j.LogManager;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import glm.vec._3.i.Vec3i;
 | 
					import glm.vec._3.i.Vec3i;
 | 
				
			||||||
import ru.windcorp.progressia.common.world.ChunkData;
 | 
					import ru.windcorp.progressia.common.world.ChunkData;
 | 
				
			||||||
 | 
					import ru.windcorp.progressia.common.world.PacketRevokeChunk;
 | 
				
			||||||
 | 
					import ru.windcorp.progressia.common.world.PacketSendChunk;
 | 
				
			||||||
import ru.windcorp.progressia.common.world.WorldData;
 | 
					import ru.windcorp.progressia.common.world.WorldData;
 | 
				
			||||||
import ru.windcorp.progressia.common.world.generic.ChunkSet;
 | 
					import ru.windcorp.progressia.common.world.generic.ChunkSet;
 | 
				
			||||||
import ru.windcorp.progressia.common.world.generic.ChunkSets;
 | 
					import ru.windcorp.progressia.common.world.generic.ChunkSets;
 | 
				
			||||||
import ru.windcorp.progressia.test.TestChunkSender;
 | 
					 | 
				
			||||||
import ru.windcorp.progressia.test.TestContent;
 | 
					import ru.windcorp.progressia.test.TestContent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class ChunkManager {
 | 
					public class ChunkManager {
 | 
				
			||||||
@@ -148,14 +149,29 @@ public class ChunkManager {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	public void sendChunk(Player player, Vec3i chunkPos) {
 | 
						public void sendChunk(Player player, Vec3i chunkPos) {
 | 
				
			||||||
		LogManager.getLogger().info("Sending {} {} {}", chunkPos.x, chunkPos.y, chunkPos.z);
 | 
							LogManager.getLogger().info("Sending {} {} {}", chunkPos.x, chunkPos.y, chunkPos.z);
 | 
				
			||||||
		TestChunkSender.sendChunk(server, player.getClient(), chunkPos);
 | 
							
 | 
				
			||||||
 | 
							ChunkData chunk = server.getWorld().getData().getChunk(chunkPos);
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							if (chunk == null) {
 | 
				
			||||||
 | 
								throw new IllegalStateException(String.format(
 | 
				
			||||||
 | 
										"Chunk (%d; %d; %d) is not loaded, cannot send",
 | 
				
			||||||
 | 
										chunkPos.x, chunkPos.y, chunkPos.z
 | 
				
			||||||
 | 
								));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							PacketSendChunk packet = new PacketSendChunk();
 | 
				
			||||||
 | 
							packet.set(chunk);		
 | 
				
			||||||
 | 
							player.getClient().sendPacket(packet);
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		getVision(player, true).visible.add(chunkPos);
 | 
							getVision(player, true).visible.add(chunkPos);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	public void revokeChunk(Player player, Vec3i chunkPos) {
 | 
						public void revokeChunk(Player player, Vec3i chunkPos) {
 | 
				
			||||||
		LogManager.getLogger().info("Revoking {} {} {}", chunkPos.x, chunkPos.y, chunkPos.z);
 | 
							LogManager.getLogger().info("Revoking {} {} {}", chunkPos.x, chunkPos.y, chunkPos.z);
 | 
				
			||||||
		TestChunkSender.revokeChunk(player.getClient(), chunkPos);
 | 
							
 | 
				
			||||||
 | 
							PacketRevokeChunk packet = new PacketRevokeChunk();
 | 
				
			||||||
 | 
							packet.set(chunkPos);
 | 
				
			||||||
 | 
							player.getClient().sendPacket(packet);
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		PlayerVision vision = getVision(player, false);
 | 
							PlayerVision vision = getVision(player, false);
 | 
				
			||||||
		if (vision != null) {
 | 
							if (vision != null) {
 | 
				
			||||||
@@ -173,6 +189,16 @@ public class ChunkManager {
 | 
				
			|||||||
		return vision.isChunkVisible(chunkPos);
 | 
							return vision.isChunkVisible(chunkPos);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
 | 
						public ChunkSet getVisibleChunks(Player player) {
 | 
				
			||||||
 | 
							PlayerVision vision = getVision(player, false);
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							if (vision == null) {
 | 
				
			||||||
 | 
								return ChunkSets.empty();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							return vision.visible;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public Server getServer() {
 | 
						public Server getServer() {
 | 
				
			||||||
		return server;
 | 
							return server;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										183
									
								
								src/main/java/ru/windcorp/progressia/server/EntityManager.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								src/main/java/ru/windcorp/progressia/server/EntityManager.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,183 @@
 | 
				
			|||||||
 | 
					package ru.windcorp.progressia.server;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.Collections;
 | 
				
			||||||
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					import java.util.WeakHashMap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.apache.logging.log4j.LogManager;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import glm.vec._3.i.Vec3i;
 | 
				
			||||||
 | 
					import gnu.trove.TCollections;
 | 
				
			||||||
 | 
					import gnu.trove.iterator.TLongIterator;
 | 
				
			||||||
 | 
					import gnu.trove.set.TLongSet;
 | 
				
			||||||
 | 
					import gnu.trove.set.hash.TLongHashSet;
 | 
				
			||||||
 | 
					import ru.windcorp.jputil.chars.StringUtil;
 | 
				
			||||||
 | 
					import ru.windcorp.progressia.common.util.Vectors;
 | 
				
			||||||
 | 
					import ru.windcorp.progressia.common.world.entity.EntityData;
 | 
				
			||||||
 | 
					import ru.windcorp.progressia.common.world.entity.PacketRevokeEntity;
 | 
				
			||||||
 | 
					import ru.windcorp.progressia.common.world.generic.ChunkSet;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class EntityManager {
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						private class PlayerVision {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private final TLongSet visible = TCollections.synchronizedSet(new TLongHashSet());
 | 
				
			||||||
 | 
							private final TLongSet requested = new TLongHashSet();
 | 
				
			||||||
 | 
							private final TLongSet toSend = new TLongHashSet();
 | 
				
			||||||
 | 
							private final TLongSet toRevoke = new TLongHashSet();
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							public boolean isEntityVisible(long entityId) {
 | 
				
			||||||
 | 
								return visible.contains(entityId);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							public void gatherRequests(Player player) {
 | 
				
			||||||
 | 
								requested.clear();
 | 
				
			||||||
 | 
								
 | 
				
			||||||
 | 
								ChunkSet visibleChunks = player.getClient().getVisibleChunks();
 | 
				
			||||||
 | 
								Vec3i v = Vectors.grab3i();
 | 
				
			||||||
 | 
								
 | 
				
			||||||
 | 
								getServer().getWorld().forEachEntity(entity -> {
 | 
				
			||||||
 | 
									if (visibleChunks.contains(entity.getChunkCoords(v))) {
 | 
				
			||||||
 | 
										requested.add(entity.getEntityId());
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
								
 | 
				
			||||||
 | 
								Vectors.release(v);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							public void updateQueues(Player player) {
 | 
				
			||||||
 | 
								toSend.clear();
 | 
				
			||||||
 | 
								toSend.addAll(requested);
 | 
				
			||||||
 | 
								toSend.removeAll(visible);
 | 
				
			||||||
 | 
								toSend.retainAll(loaded);
 | 
				
			||||||
 | 
								
 | 
				
			||||||
 | 
								toRevoke.clear();
 | 
				
			||||||
 | 
								
 | 
				
			||||||
 | 
								for (TLongIterator it = visible.iterator(); it.hasNext();) {
 | 
				
			||||||
 | 
									long entityId = it.next();
 | 
				
			||||||
 | 
									if (!loaded.contains(entityId) || !requested.contains(entityId)) {
 | 
				
			||||||
 | 
										toRevoke.add(entityId);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							public void processQueues(Player player) {
 | 
				
			||||||
 | 
								toRevoke.forEach(entityId -> {
 | 
				
			||||||
 | 
									revokeEntity(player, entityId);
 | 
				
			||||||
 | 
									return true;
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
								toRevoke.clear();
 | 
				
			||||||
 | 
								
 | 
				
			||||||
 | 
								toSend.forEach(entityId -> {
 | 
				
			||||||
 | 
									sendEntity(player, entityId);
 | 
				
			||||||
 | 
									return true;
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
								toSend.clear();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private final Server server;
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						private final TLongSet loaded;
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// TODO replace with a normal Map managed by some sort of PlayerListener, weak maps are weak
 | 
				
			||||||
 | 
						private final Map<Player, PlayerVision> visions = Collections.synchronizedMap(new WeakHashMap<>());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public EntityManager(Server server) {
 | 
				
			||||||
 | 
							this.server = server;
 | 
				
			||||||
 | 
							this.loaded = server.getWorld().getData().getLoadedEntities();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public void tick() {
 | 
				
			||||||
 | 
							synchronized (getServer().getWorld().getData()) {
 | 
				
			||||||
 | 
								synchronized (visions) {
 | 
				
			||||||
 | 
									gatherRequests();
 | 
				
			||||||
 | 
									updateQueues();
 | 
				
			||||||
 | 
									processQueues();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private void gatherRequests() {
 | 
				
			||||||
 | 
							server.getPlayerManager().getPlayers().forEach(p -> {
 | 
				
			||||||
 | 
								PlayerVision vision = getVision(p, true);
 | 
				
			||||||
 | 
								vision.gatherRequests(p);
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						private void updateQueues() {
 | 
				
			||||||
 | 
							visions.forEach((p, v) -> {
 | 
				
			||||||
 | 
								v.updateQueues(p);
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private void processQueues() {
 | 
				
			||||||
 | 
							visions.forEach((p, v) -> {
 | 
				
			||||||
 | 
								v.processQueues(p);
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						private PlayerVision getVision(Player player, boolean createIfMissing) {
 | 
				
			||||||
 | 
							return createIfMissing ? visions.computeIfAbsent(player, k -> new PlayerVision()) : visions.get(player);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						public void sendEntity(Player player, long entityId) {
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							EntityData entity = server.getWorld().getData().getEntity(entityId);
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							if (entity == null) {
 | 
				
			||||||
 | 
								throw new IllegalStateException(
 | 
				
			||||||
 | 
										"Entity with entity ID " + new String(StringUtil.toFullHex(entityId)) + " is not loaded, cannot send"
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							LogManager.getLogger().info("Sending {}", entity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							PacketSendEntity packet = new PacketSendEntity();
 | 
				
			||||||
 | 
							packet.set(entity);
 | 
				
			||||||
 | 
							player.getClient().sendPacket(packet);
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							getVision(player, true).visible.add(entityId);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						public void revokeEntity(Player player, long entityId) {
 | 
				
			||||||
 | 
							LogManager.getLogger().info("Revoking {}", new String(StringUtil.toFullHex(entityId)));
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							PacketRevokeEntity packet = new PacketRevokeEntity();
 | 
				
			||||||
 | 
							packet.set(entityId);
 | 
				
			||||||
 | 
							player.getClient().sendPacket(packet);
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							PlayerVision vision = getVision(player, false);
 | 
				
			||||||
 | 
							if (vision != null) {
 | 
				
			||||||
 | 
								vision.visible.remove(entityId);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						public boolean isEntityVisible(long entityId, Player player) {
 | 
				
			||||||
 | 
							PlayerVision vision = getVision(player, false);
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							if (vision == null) {
 | 
				
			||||||
 | 
								return false;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							return vision.isEntityVisible(entityId);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						private static final TLongSet EMPTY_LONG_SET = TCollections.unmodifiableSet(new TLongHashSet());
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						public TLongSet getVisibleEntities(Player player) {
 | 
				
			||||||
 | 
							PlayerVision vision = getVision(player, false);
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							if (vision == null) {
 | 
				
			||||||
 | 
								return EMPTY_LONG_SET;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							return vision.visible;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						public Server getServer() {
 | 
				
			||||||
 | 
							return server;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,70 @@
 | 
				
			|||||||
 | 
					package ru.windcorp.progressia.server;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.io.DataInput;
 | 
				
			||||||
 | 
					import java.io.DataOutput;
 | 
				
			||||||
 | 
					import java.io.IOException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import ru.windcorp.progressia.common.state.IOContext;
 | 
				
			||||||
 | 
					import ru.windcorp.progressia.common.util.DataBuffer;
 | 
				
			||||||
 | 
					import ru.windcorp.progressia.common.util.crash.CrashReports;
 | 
				
			||||||
 | 
					import ru.windcorp.progressia.common.world.DecodingException;
 | 
				
			||||||
 | 
					import ru.windcorp.progressia.common.world.PacketWorldChange;
 | 
				
			||||||
 | 
					import ru.windcorp.progressia.common.world.WorldData;
 | 
				
			||||||
 | 
					import ru.windcorp.progressia.common.world.entity.EntityData;
 | 
				
			||||||
 | 
					import ru.windcorp.progressia.common.world.entity.EntityDataRegistry;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class PacketSendEntity extends PacketWorldChange {
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						private String id;
 | 
				
			||||||
 | 
						private long entityId;
 | 
				
			||||||
 | 
						private final DataBuffer buffer = new DataBuffer();
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						public PacketSendEntity() {
 | 
				
			||||||
 | 
							this("Core:SendEntity");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						protected PacketSendEntity(String id) {
 | 
				
			||||||
 | 
							super(id);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						public void set(EntityData entity) {
 | 
				
			||||||
 | 
							this.id = entity.getId();
 | 
				
			||||||
 | 
							this.entityId = entity.getEntityId();
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
 | 
								entity.write(this.buffer.getWriter(), IOContext.COMMS);
 | 
				
			||||||
 | 
							} catch (IOException e) {
 | 
				
			||||||
 | 
								CrashReports.report(e, "Could not write an entity into an internal buffer");
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void read(DataInput input) throws IOException, DecodingException {
 | 
				
			||||||
 | 
							this.id = input.readUTF();
 | 
				
			||||||
 | 
							this.entityId = input.readLong();
 | 
				
			||||||
 | 
							this.buffer.fill(input, input.readInt());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void write(DataOutput output) throws IOException {
 | 
				
			||||||
 | 
							output.writeUTF(this.id);
 | 
				
			||||||
 | 
							output.writeLong(this.entityId);
 | 
				
			||||||
 | 
							output.writeInt(this.buffer.getSize());
 | 
				
			||||||
 | 
							this.buffer.flush(output);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void apply(WorldData world) {
 | 
				
			||||||
 | 
							EntityData entity = EntityDataRegistry.getInstance().create(this.id);
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							entity.setEntityId(this.entityId);
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
 | 
								entity.read(this.buffer.getReader(), IOContext.COMMS);
 | 
				
			||||||
 | 
							} catch (IOException e) {
 | 
				
			||||||
 | 
								CrashReports.report(e, "Could not read an entity from an internal buffer");
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							world.addEntity(entity);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -55,4 +55,8 @@ public class Player extends PlayerData implements ChunkLoader {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public String getLogin() {
 | 
				
			||||||
 | 
							return getClient().getLogin();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,10 +4,13 @@ import java.util.ArrayList;
 | 
				
			|||||||
import java.util.Collection;
 | 
					import java.util.Collection;
 | 
				
			||||||
import java.util.Collections;
 | 
					import java.util.Collections;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import glm.vec._2.Vec2;
 | 
				
			||||||
 | 
					import glm.vec._3.Vec3;
 | 
				
			||||||
import glm.vec._3.i.Vec3i;
 | 
					import glm.vec._3.i.Vec3i;
 | 
				
			||||||
import ru.windcorp.progressia.common.util.Vectors;
 | 
					import ru.windcorp.progressia.common.util.Vectors;
 | 
				
			||||||
import ru.windcorp.progressia.common.util.crash.CrashReports;
 | 
					import ru.windcorp.progressia.common.util.crash.CrashReports;
 | 
				
			||||||
import ru.windcorp.progressia.common.world.entity.EntityData;
 | 
					import ru.windcorp.progressia.common.world.entity.EntityData;
 | 
				
			||||||
 | 
					import ru.windcorp.progressia.common.world.entity.EntityDataRegistry;
 | 
				
			||||||
import ru.windcorp.progressia.test.TestContent;
 | 
					import ru.windcorp.progressia.test.TestContent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class PlayerManager {
 | 
					public class PlayerManager {
 | 
				
			||||||
@@ -38,13 +41,28 @@ public class PlayerManager {
 | 
				
			|||||||
				getServer().getChunkManager().loadChunk(chunkPos);
 | 
									getServer().getChunkManager().loadChunk(chunkPos);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			
 | 
								
 | 
				
			||||||
			return getServer().getWorld().getData().getEntity(TestContent.PLAYER_ENTITY_ID);
 | 
								EntityData entity = spawnPlayerEntity(login);
 | 
				
			||||||
 | 
								return entity;
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			CrashReports.report(null, "Unknown login %s, javahorse stupid", login);
 | 
								CrashReports.report(null, "Unknown login %s, javahorse stupid", login);
 | 
				
			||||||
			return null;
 | 
								return null;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
 | 
						private EntityData spawnPlayerEntity(String login) {
 | 
				
			||||||
 | 
							EntityData player = EntityDataRegistry.getInstance().create("Test:Player");
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							player.setEntityId(TestContent.PLAYER_ENTITY_ID);
 | 
				
			||||||
 | 
							player.setPosition(new Vec3(8, 8, 8));
 | 
				
			||||||
 | 
							player.setDirection(new Vec2(
 | 
				
			||||||
 | 
									Math.toRadians(40), Math.toRadians(10)
 | 
				
			||||||
 | 
							));
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							getServer().getWorld().getData().addEntity(player);
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							return player;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public Server getServer() {
 | 
						public Server getServer() {
 | 
				
			||||||
		return server;
 | 
							return server;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -32,6 +32,7 @@ public class Server {
 | 
				
			|||||||
	private final ClientManager clientManager;
 | 
						private final ClientManager clientManager;
 | 
				
			||||||
	private final PlayerManager playerManager;
 | 
						private final PlayerManager playerManager;
 | 
				
			||||||
	private final  ChunkManager  chunkManager;
 | 
						private final  ChunkManager  chunkManager;
 | 
				
			||||||
 | 
						private final EntityManager entityManager;
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	private final TaskQueue taskQueue = new TaskQueue(this::isServerThread);
 | 
						private final TaskQueue taskQueue = new TaskQueue(this::isServerThread);
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
@@ -44,9 +45,11 @@ public class Server {
 | 
				
			|||||||
		this.clientManager = new ClientManager(this);
 | 
							this.clientManager = new ClientManager(this);
 | 
				
			||||||
		this.playerManager = new PlayerManager(this);
 | 
							this.playerManager = new PlayerManager(this);
 | 
				
			||||||
		this.chunkManager = new ChunkManager(this);
 | 
							this.chunkManager = new ChunkManager(this);
 | 
				
			||||||
 | 
							this.entityManager = new EntityManager(this);
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		schedule(this::scheduleChunkTicks);
 | 
							schedule(this::scheduleWorldTicks);
 | 
				
			||||||
		schedule(chunkManager::tick);
 | 
							schedule(chunkManager::tick);
 | 
				
			||||||
 | 
							schedule(entityManager::tick);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
@@ -197,8 +200,9 @@ public class Server {
 | 
				
			|||||||
		serverThread.stop();
 | 
							serverThread.stop();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	private void scheduleChunkTicks(Server server) {
 | 
						private void scheduleWorldTicks(Server server) {
 | 
				
			||||||
		server.getWorld().getChunks().forEach(chunk -> requestEvaluation(chunk.getTickTask()));
 | 
							server.getWorld().getChunks().forEach(chunk -> requestEvaluation(chunk.getTickTask()));
 | 
				
			||||||
 | 
							requestEvaluation(server.getWorld().getTickEntitiesTask());
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,7 +16,7 @@ public class ServerState {
 | 
				
			|||||||
	
 | 
						
 | 
				
			||||||
	public static void startServer() {
 | 
						public static void startServer() {
 | 
				
			||||||
		Server server = new Server(new WorldData());
 | 
							Server server = new Server(new WorldData());
 | 
				
			||||||
		server.getWorld().getData().tmp_generate();
 | 
					//		server.getWorld().getData().tmp_generate();
 | 
				
			||||||
		setInstance(server);
 | 
							setInstance(server);
 | 
				
			||||||
		server.start();
 | 
							server.start();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,8 @@ import java.util.Collection;
 | 
				
			|||||||
import java.util.Collections;
 | 
					import java.util.Collections;
 | 
				
			||||||
import java.util.concurrent.atomic.AtomicInteger;
 | 
					import java.util.concurrent.atomic.AtomicInteger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.apache.logging.log4j.LogManager;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import glm.vec._3.i.Vec3i;
 | 
					import glm.vec._3.i.Vec3i;
 | 
				
			||||||
import gnu.trove.TCollections;
 | 
					import gnu.trove.TCollections;
 | 
				
			||||||
import gnu.trove.map.TIntObjectMap;
 | 
					import gnu.trove.map.TIntObjectMap;
 | 
				
			||||||
@@ -41,10 +43,10 @@ public class ClientManager {
 | 
				
			|||||||
			
 | 
								
 | 
				
			||||||
			if (client instanceof ClientChat) {
 | 
								if (client instanceof ClientChat) {
 | 
				
			||||||
				addClientChat((ClientChat) client);
 | 
									addClientChat((ClientChat) client);
 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
				
 | 
									
 | 
				
			||||||
			if (client instanceof ClientPlayer) {
 | 
									if (client instanceof ClientPlayer) {
 | 
				
			||||||
				addClientPlayer((ClientPlayer) client);
 | 
										addClientPlayer((ClientPlayer) client);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			
 | 
								
 | 
				
			||||||
			client.addListener(new DefaultServerCommsListener(this, client));
 | 
								client.addListener(new DefaultServerCommsListener(this, client));
 | 
				
			||||||
@@ -58,18 +60,12 @@ public class ClientManager {
 | 
				
			|||||||
	private void addClientPlayer(ClientPlayer client) {
 | 
						private void addClientPlayer(ClientPlayer client) {
 | 
				
			||||||
		String login = client.getLogin();
 | 
							String login = client.getLogin();
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		EntityData entity;
 | 
							EntityData entity = getServer().getPlayerManager().conjurePlayerEntity(login);
 | 
				
			||||||
		synchronized (getServer().getWorld().getData()) {
 | 
							Player player = new Player(entity, getServer(), client);
 | 
				
			||||||
			entity = getServer().getPlayerManager().conjurePlayerEntity(login);
 | 
							getServer().getPlayerManager().getPlayers().add(player);
 | 
				
			||||||
			
 | 
					 | 
				
			||||||
			Player player = new Player(entity, getServer(), client);
 | 
					 | 
				
			||||||
			
 | 
					 | 
				
			||||||
			getServer().getPlayerManager().getPlayers().add(player);
 | 
					 | 
				
			||||||
			
 | 
					 | 
				
			||||||
			getServer().getChunkManager().sendChunk(player, entity.getChunkCoords(null));
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		PacketSetLocalPlayer packet = new PacketSetLocalPlayer();
 | 
							PacketSetLocalPlayer packet = new PacketSetLocalPlayer();
 | 
				
			||||||
 | 
							LogManager.getLogger().info("Sending local player ID {}", EntityData.formatEntityId(entity.getEntityId()));
 | 
				
			||||||
		packet.set(entity.getEntityId());
 | 
							packet.set(entity.getEntityId());
 | 
				
			||||||
		client.sendPacket(packet);
 | 
							client.sendPacket(packet);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,8 @@
 | 
				
			|||||||
package ru.windcorp.progressia.server.comms;
 | 
					package ru.windcorp.progressia.server.comms;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import glm.vec._3.i.Vec3i;
 | 
					import glm.vec._3.i.Vec3i;
 | 
				
			||||||
 | 
					import ru.windcorp.progressia.common.world.generic.ChunkSet;
 | 
				
			||||||
 | 
					import ru.windcorp.progressia.common.world.generic.ChunkSets;
 | 
				
			||||||
import ru.windcorp.progressia.server.Player;
 | 
					import ru.windcorp.progressia.server.Player;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public abstract class ClientPlayer extends ClientChat {
 | 
					public abstract class ClientPlayer extends ClientChat {
 | 
				
			||||||
@@ -26,6 +28,11 @@ public abstract class ClientPlayer extends ClientChat {
 | 
				
			|||||||
		return player.getServer().getChunkManager().isChunkVisible(chunkPos, player);
 | 
							return player.getServer().getChunkManager().isChunkVisible(chunkPos, player);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
 | 
						public ChunkSet getVisibleChunks() {
 | 
				
			||||||
 | 
							if (player == null) return ChunkSets.empty();
 | 
				
			||||||
 | 
							return player.getServer().getChunkManager().getVisibleChunks(player);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
	public boolean isChunkVisible(long entityId) {
 | 
						public boolean isChunkVisible(long entityId) {
 | 
				
			||||||
		return true;
 | 
							return true;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,15 +11,12 @@ import glm.vec._3.i.Vec3i;
 | 
				
			|||||||
import ru.windcorp.progressia.common.world.ChunkData;
 | 
					import ru.windcorp.progressia.common.world.ChunkData;
 | 
				
			||||||
import ru.windcorp.progressia.common.world.Coordinates;
 | 
					import ru.windcorp.progressia.common.world.Coordinates;
 | 
				
			||||||
import ru.windcorp.progressia.common.world.block.BlockFace;
 | 
					import ru.windcorp.progressia.common.world.block.BlockFace;
 | 
				
			||||||
import ru.windcorp.progressia.common.world.entity.EntityData;
 | 
					 | 
				
			||||||
import ru.windcorp.progressia.common.world.generic.GenericChunk;
 | 
					import ru.windcorp.progressia.common.world.generic.GenericChunk;
 | 
				
			||||||
import ru.windcorp.progressia.common.world.tile.TileDataStack;
 | 
					import ru.windcorp.progressia.common.world.tile.TileDataStack;
 | 
				
			||||||
import ru.windcorp.progressia.common.world.tile.TileReference;
 | 
					import ru.windcorp.progressia.common.world.tile.TileReference;
 | 
				
			||||||
import ru.windcorp.progressia.server.world.block.BlockLogic;
 | 
					import ru.windcorp.progressia.server.world.block.BlockLogic;
 | 
				
			||||||
import ru.windcorp.progressia.server.world.block.BlockLogicRegistry;
 | 
					import ru.windcorp.progressia.server.world.block.BlockLogicRegistry;
 | 
				
			||||||
import ru.windcorp.progressia.server.world.block.TickableBlock;
 | 
					import ru.windcorp.progressia.server.world.block.TickableBlock;
 | 
				
			||||||
import ru.windcorp.progressia.server.world.entity.EntityLogic;
 | 
					 | 
				
			||||||
import ru.windcorp.progressia.server.world.entity.EntityLogicRegistry;
 | 
					 | 
				
			||||||
import ru.windcorp.progressia.server.world.tasks.TickChunk;
 | 
					import ru.windcorp.progressia.server.world.tasks.TickChunk;
 | 
				
			||||||
import ru.windcorp.progressia.server.world.ticking.TickingPolicy;
 | 
					import ru.windcorp.progressia.server.world.ticking.TickingPolicy;
 | 
				
			||||||
import ru.windcorp.progressia.server.world.tile.TickableTile;
 | 
					import ru.windcorp.progressia.server.world.tile.TickableTile;
 | 
				
			||||||
@@ -112,15 +109,6 @@ public class ChunkLogic implements GenericChunk<
 | 
				
			|||||||
		});
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	public void forEachEntity(BiConsumer<EntityLogic, EntityData> action) {
 | 
					 | 
				
			||||||
		getData().forEachEntity(data -> {
 | 
					 | 
				
			||||||
			action.accept(
 | 
					 | 
				
			||||||
					EntityLogicRegistry.getInstance().get(data.getId()),
 | 
					 | 
				
			||||||
					data
 | 
					 | 
				
			||||||
			);
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	public TickChunk getTickTask() {
 | 
						public TickChunk getTickTask() {
 | 
				
			||||||
		return tickTask;
 | 
							return tickTask;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,6 +13,8 @@ import ru.windcorp.progressia.common.world.entity.EntityData;
 | 
				
			|||||||
import ru.windcorp.progressia.common.world.generic.GenericWorld;
 | 
					import ru.windcorp.progressia.common.world.generic.GenericWorld;
 | 
				
			||||||
import ru.windcorp.progressia.server.Server;
 | 
					import ru.windcorp.progressia.server.Server;
 | 
				
			||||||
import ru.windcorp.progressia.server.world.block.BlockLogic;
 | 
					import ru.windcorp.progressia.server.world.block.BlockLogic;
 | 
				
			||||||
 | 
					import ru.windcorp.progressia.server.world.tasks.TickEntitiesTask;
 | 
				
			||||||
 | 
					import ru.windcorp.progressia.server.world.ticking.Evaluation;
 | 
				
			||||||
import ru.windcorp.progressia.server.world.tile.TileLogic;
 | 
					import ru.windcorp.progressia.server.world.tile.TileLogic;
 | 
				
			||||||
import ru.windcorp.progressia.server.world.tile.TileLogicStack;
 | 
					import ru.windcorp.progressia.server.world.tile.TileLogicStack;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -30,6 +32,8 @@ implements GenericWorld<
 | 
				
			|||||||
	
 | 
						
 | 
				
			||||||
	private final Map<ChunkData, ChunkLogic> chunks = new HashMap<>();
 | 
						private final Map<ChunkData, ChunkLogic> chunks = new HashMap<>();
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
 | 
						private final Evaluation tickEntitiesTask = new TickEntitiesTask();
 | 
				
			||||||
 | 
						
 | 
				
			||||||
	public WorldLogic(WorldData data, Server server) {
 | 
						public WorldLogic(WorldData data, Server server) {
 | 
				
			||||||
		this.data = data;
 | 
							this.data = data;
 | 
				
			||||||
		this.server = server;
 | 
							this.server = server;
 | 
				
			||||||
@@ -64,6 +68,10 @@ implements GenericWorld<
 | 
				
			|||||||
		return getData().getEntities();
 | 
							return getData().getEntities();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
 | 
						public Evaluation getTickEntitiesTask() {
 | 
				
			||||||
 | 
							return tickEntitiesTask;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
	public Server getServer() {
 | 
						public Server getServer() {
 | 
				
			||||||
		return server;
 | 
							return server;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,7 +14,6 @@ import ru.windcorp.progressia.common.world.block.BlockFace;
 | 
				
			|||||||
import ru.windcorp.progressia.common.world.tile.TileDataStack;
 | 
					import ru.windcorp.progressia.common.world.tile.TileDataStack;
 | 
				
			||||||
import ru.windcorp.progressia.server.Server;
 | 
					import ru.windcorp.progressia.server.Server;
 | 
				
			||||||
import ru.windcorp.progressia.server.world.ChunkLogic;
 | 
					import ru.windcorp.progressia.server.world.ChunkLogic;
 | 
				
			||||||
import ru.windcorp.progressia.server.world.TickAndUpdateUtil;
 | 
					 | 
				
			||||||
import ru.windcorp.progressia.server.world.TickContextMutable;
 | 
					import ru.windcorp.progressia.server.world.TickContextMutable;
 | 
				
			||||||
import ru.windcorp.progressia.server.world.block.BlockLogic;
 | 
					import ru.windcorp.progressia.server.world.block.BlockLogic;
 | 
				
			||||||
import ru.windcorp.progressia.server.world.block.TickableBlock;
 | 
					import ru.windcorp.progressia.server.world.block.TickableBlock;
 | 
				
			||||||
@@ -55,7 +54,6 @@ public class TickChunk extends Evaluation {
 | 
				
			|||||||
	public void evaluate(Server server) {
 | 
						public void evaluate(Server server) {
 | 
				
			||||||
		tickRegulars(server);
 | 
							tickRegulars(server);
 | 
				
			||||||
		tickRandom(server);
 | 
							tickRandom(server);
 | 
				
			||||||
		tickEntities(server);
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private void tickRegulars(Server server) {
 | 
						private void tickRegulars(Server server) {
 | 
				
			||||||
@@ -165,12 +163,6 @@ public class TickChunk extends Evaluation {
 | 
				
			|||||||
		);
 | 
							);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private void tickEntities(Server server) {
 | 
					 | 
				
			||||||
		chunk.getData().forEachEntity(entity -> {
 | 
					 | 
				
			||||||
			TickAndUpdateUtil.tickEntity(entity, server);
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public void getRelevantChunk(Vec3i output) {
 | 
						public void getRelevantChunk(Vec3i output) {
 | 
				
			||||||
		Vec3i p = chunk.getData().getPosition();
 | 
							Vec3i p = chunk.getData().getPosition();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					package ru.windcorp.progressia.server.world.tasks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import glm.vec._3.i.Vec3i;
 | 
				
			||||||
 | 
					import ru.windcorp.progressia.server.Server;
 | 
				
			||||||
 | 
					import ru.windcorp.progressia.server.world.TickAndUpdateUtil;
 | 
				
			||||||
 | 
					import ru.windcorp.progressia.server.world.ticking.Evaluation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class TickEntitiesTask extends Evaluation {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void evaluate(Server server) {
 | 
				
			||||||
 | 
							server.getWorld().forEachEntity(entity -> {
 | 
				
			||||||
 | 
								TickAndUpdateUtil.tickEntity(entity, server);
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void getRelevantChunk(Vec3i output) {
 | 
				
			||||||
 | 
							// Do nothing
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public boolean isThreadSensitive() {
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,44 +0,0 @@
 | 
				
			|||||||
package ru.windcorp.progressia.test;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import java.io.IOException;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import glm.vec._3.i.Vec3i;
 | 
					 | 
				
			||||||
import ru.windcorp.progressia.common.io.ChunkIO;
 | 
					 | 
				
			||||||
import ru.windcorp.progressia.common.util.crash.CrashReports;
 | 
					 | 
				
			||||||
import ru.windcorp.progressia.common.world.ChunkData;
 | 
					 | 
				
			||||||
import ru.windcorp.progressia.common.world.PacketRevokeChunk;
 | 
					 | 
				
			||||||
import ru.windcorp.progressia.common.world.PacketSendChunk;
 | 
					 | 
				
			||||||
import ru.windcorp.progressia.server.Server;
 | 
					 | 
				
			||||||
import ru.windcorp.progressia.server.comms.ClientPlayer;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public class TestChunkSender {
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	public static void sendChunk(Server server, ClientPlayer receiver, Vec3i chunkPos) {
 | 
					 | 
				
			||||||
		ChunkData chunk = server.getWorld().getData().getChunk(chunkPos);
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		if (chunk == null) {
 | 
					 | 
				
			||||||
			throw new IllegalStateException(String.format(
 | 
					 | 
				
			||||||
					"Chunk (%d; %d; %d) is not loaded, cannot send",
 | 
					 | 
				
			||||||
					chunkPos.x, chunkPos.y, chunkPos.z
 | 
					 | 
				
			||||||
			));
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		PacketSendChunk packet = new PacketSendChunk();
 | 
					 | 
				
			||||||
		packet.getPosition().set(chunkPos.x, chunkPos.y, chunkPos.z);
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		try {
 | 
					 | 
				
			||||||
			ChunkIO.save(chunk, packet.getData().getOutputStream());
 | 
					 | 
				
			||||||
		} catch (IOException e) {
 | 
					 | 
				
			||||||
			CrashReports.report(e, "TestChunkSender fjcked up. javahorse stupid");
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		receiver.sendPacket(packet);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public static void revokeChunk(ClientPlayer receiver, Vec3i chunkPos) {
 | 
					 | 
				
			||||||
		PacketRevokeChunk packet = new PacketRevokeChunk();
 | 
					 | 
				
			||||||
		packet.set(chunkPos);
 | 
					 | 
				
			||||||
		receiver.sendPacket(packet);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -7,16 +7,12 @@ import java.util.function.Consumer;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import org.lwjgl.glfw.GLFW;
 | 
					import org.lwjgl.glfw.GLFW;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import glm.Glm;
 | 
					 | 
				
			||||||
import glm.vec._2.Vec2;
 | 
					 | 
				
			||||||
import glm.vec._3.Vec3;
 | 
					 | 
				
			||||||
import glm.vec._3.i.Vec3i;
 | 
					import glm.vec._3.i.Vec3i;
 | 
				
			||||||
import ru.windcorp.progressia.client.ClientState;
 | 
					import ru.windcorp.progressia.client.ClientState;
 | 
				
			||||||
import ru.windcorp.progressia.client.audio.SoundEffect;
 | 
					import ru.windcorp.progressia.client.audio.SoundEffect;
 | 
				
			||||||
import ru.windcorp.progressia.client.comms.controls.*;
 | 
					import ru.windcorp.progressia.client.comms.controls.*;
 | 
				
			||||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
 | 
					import ru.windcorp.progressia.client.graphics.input.KeyEvent;
 | 
				
			||||||
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
 | 
					import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
 | 
				
			||||||
import ru.windcorp.progressia.client.graphics.world.LocalPlayer;
 | 
					 | 
				
			||||||
import ru.windcorp.progressia.client.graphics.world.Selection;
 | 
					import ru.windcorp.progressia.client.graphics.world.Selection;
 | 
				
			||||||
import ru.windcorp.progressia.client.world.block.*;
 | 
					import ru.windcorp.progressia.client.world.block.*;
 | 
				
			||||||
import ru.windcorp.progressia.client.world.entity.*;
 | 
					import ru.windcorp.progressia.client.world.entity.*;
 | 
				
			||||||
@@ -26,7 +22,6 @@ import ru.windcorp.progressia.common.collision.CollisionModel;
 | 
				
			|||||||
import ru.windcorp.progressia.common.comms.controls.*;
 | 
					import ru.windcorp.progressia.common.comms.controls.*;
 | 
				
			||||||
import ru.windcorp.progressia.common.io.ChunkIO;
 | 
					import ru.windcorp.progressia.common.io.ChunkIO;
 | 
				
			||||||
import ru.windcorp.progressia.common.state.StatefulObjectRegistry.Factory;
 | 
					import ru.windcorp.progressia.common.state.StatefulObjectRegistry.Factory;
 | 
				
			||||||
import ru.windcorp.progressia.common.util.Vectors;
 | 
					 | 
				
			||||||
import ru.windcorp.progressia.common.world.ChunkData;
 | 
					import ru.windcorp.progressia.common.world.ChunkData;
 | 
				
			||||||
import ru.windcorp.progressia.common.world.Coordinates;
 | 
					import ru.windcorp.progressia.common.world.Coordinates;
 | 
				
			||||||
import ru.windcorp.progressia.common.world.block.*;
 | 
					import ru.windcorp.progressia.common.world.block.*;
 | 
				
			||||||
@@ -214,12 +209,9 @@ public class TestContent {
 | 
				
			|||||||
	
 | 
						
 | 
				
			||||||
	private static Selection getSelection() {
 | 
						private static Selection getSelection() {
 | 
				
			||||||
		ru.windcorp.progressia.client.Client client = ClientState.getInstance();
 | 
							ru.windcorp.progressia.client.Client client = ClientState.getInstance();
 | 
				
			||||||
		if (client == null) return null;
 | 
							if (client == null || !client.isReady()) return null;
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		LocalPlayer player = client.getLocalPlayer();
 | 
							return client.getLocalPlayer().getSelection();
 | 
				
			||||||
		if (player == null) return null;
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		return player.getSelection();
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	private static void onBlockBreakTrigger(ControlData control) {
 | 
						private static void onBlockBreakTrigger(ControlData control) {
 | 
				
			||||||
@@ -336,21 +328,6 @@ public class TestContent {
 | 
				
			|||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		if (Glm.equals(chunk.getPosition(), Vectors.ZERO_3i)) {
 | 
					 | 
				
			||||||
			EntityData player = EntityDataRegistry.getInstance().create("Test:Player");
 | 
					 | 
				
			||||||
			player.setEntityId(PLAYER_ENTITY_ID);
 | 
					 | 
				
			||||||
			player.setPosition(new Vec3(8, 8, 8));
 | 
					 | 
				
			||||||
			player.setDirection(new Vec2(
 | 
					 | 
				
			||||||
					(float) Math.toRadians(40), (float) Math.toRadians(45)
 | 
					 | 
				
			||||||
			));
 | 
					 | 
				
			||||||
			chunk.getEntities().add(player);
 | 
					 | 
				
			||||||
			
 | 
					 | 
				
			||||||
//			EntityData statie = EntityDataRegistry.getInstance().create("Test:Statie");
 | 
					 | 
				
			||||||
//			statie.setEntityId(STATIE_ENTITY_ID);
 | 
					 | 
				
			||||||
//			statie.setPosition(new Vec3(0, 15, 16));
 | 
					 | 
				
			||||||
//			chunk.getEntities().add(statie);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private static void registerMisc() {
 | 
						private static void registerMisc() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -61,7 +61,7 @@ public class TestPlayerControls {
 | 
				
			|||||||
	private Runnable updateCallback = null;
 | 
						private Runnable updateCallback = null;
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	public void applyPlayerControls() {
 | 
						public void applyPlayerControls() {
 | 
				
			||||||
		if (ClientState.getInstance() == null || ClientState.getInstance().getLocalPlayer() == null) {
 | 
							if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
 | 
				
			||||||
			return;
 | 
								return;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
@@ -107,7 +107,7 @@ public class TestPlayerControls {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	public void handleInput(Input input) {
 | 
						public void handleInput(Input input) {
 | 
				
			||||||
		if (ClientState.getInstance() == null || ClientState.getInstance().getLocalPlayer() == null) {
 | 
							if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) {
 | 
				
			||||||
			return;
 | 
								return;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user