From b44540999bfd3c91db59bce213371328f8f1a896 Mon Sep 17 00:00:00 2001 From: OLEGSHA Date: Tue, 29 Dec 2020 16:40:37 +0300 Subject: [PATCH] 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 --- .../ru/windcorp/progressia/client/Client.java | 24 ++- .../comms/DefaultClientCommsListener.java | 21 +- .../client/graphics/world/LayerWorld.java | 9 +- .../client/graphics/world/LocalPlayer.java | 51 ++++- .../client/world/entity/NPedModel.java | 2 + .../progressia/common/world/ChunkData.java | 13 -- .../progressia/common/world/WorldData.java | 60 ++++-- .../common/world/WorldDataListener.java | 15 ++ .../common/world/entity/EntityData.java | 23 +++ .../world/entity/PacketRevokeEntity.java | 41 ++++ .../common/world/generic/ChunkSets.java | 76 ++++++++ .../progressia/server/ChunkManager.java | 32 ++- .../progressia/server/EntityManager.java | 183 ++++++++++++++++++ .../progressia/server/PacketSendEntity.java | 70 +++++++ .../ru/windcorp/progressia/server/Player.java | 4 + .../progressia/server/PlayerManager.java | 20 +- .../ru/windcorp/progressia/server/Server.java | 8 +- .../progressia/server/ServerState.java | 2 +- .../server/comms/ClientManager.java | 24 +-- .../progressia/server/comms/ClientPlayer.java | 7 + .../progressia/server/world/ChunkLogic.java | 12 -- .../progressia/server/world/WorldLogic.java | 8 + .../server/world/tasks/TickChunk.java | 8 - .../server/world/tasks/TickEntitiesTask.java | 27 +++ .../progressia/test/TestChunkSender.java | 44 ----- .../windcorp/progressia/test/TestContent.java | 27 +-- .../progressia/test/TestPlayerControls.java | 4 +- 27 files changed, 647 insertions(+), 168 deletions(-) create mode 100644 src/main/java/ru/windcorp/progressia/common/world/entity/PacketRevokeEntity.java create mode 100644 src/main/java/ru/windcorp/progressia/server/EntityManager.java create mode 100644 src/main/java/ru/windcorp/progressia/server/PacketSendEntity.java create mode 100644 src/main/java/ru/windcorp/progressia/server/world/tasks/TickEntitiesTask.java delete mode 100644 src/main/java/ru/windcorp/progressia/test/TestChunkSender.java diff --git a/src/main/java/ru/windcorp/progressia/client/Client.java b/src/main/java/ru/windcorp/progressia/client/Client.java index cd8c5a4..539e741 100644 --- a/src/main/java/ru/windcorp/progressia/client/Client.java +++ b/src/main/java/ru/windcorp/progressia/client/Client.java @@ -1,8 +1,11 @@ package ru.windcorp.progressia.client; +import org.apache.logging.log4j.LogManager; + import ru.windcorp.progressia.client.comms.DefaultClientCommsListener; import ru.windcorp.progressia.client.comms.ServerCommsChannel; 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.world.WorldRender; import ru.windcorp.progressia.common.world.WorldData; @@ -11,7 +14,7 @@ import ru.windcorp.progressia.common.world.entity.EntityData; public class Client { private final WorldRender world; - private LocalPlayer localPlayer; + private final LocalPlayer localPlayer = new LocalPlayer(this); private final Camera camera = new Camera((float) Math.toRadians(70)); @@ -27,13 +30,13 @@ public class Client { public WorldRender getWorld() { return world; } - + public LocalPlayer getLocalPlayer() { return localPlayer; } - public void setLocalPlayer(EntityData localPlayer) { - this.localPlayer = new LocalPlayer(localPlayer); + public boolean isReady() { + return localPlayer.hasEntity(); } public Camera getCamera() { @@ -44,4 +47,17 @@ public class Client { 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) + )); + } + } diff --git a/src/main/java/ru/windcorp/progressia/client/comms/DefaultClientCommsListener.java b/src/main/java/ru/windcorp/progressia/client/comms/DefaultClientCommsListener.java index b64798e..697a0ac 100644 --- a/src/main/java/ru/windcorp/progressia/client/comms/DefaultClientCommsListener.java +++ b/src/main/java/ru/windcorp/progressia/client/comms/DefaultClientCommsListener.java @@ -2,15 +2,12 @@ package ru.windcorp.progressia.client.comms; import java.io.IOException; -import ru.windcorp.jputil.chars.StringUtil; 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.packets.Packet; import ru.windcorp.progressia.common.util.crash.CrashReports; import ru.windcorp.progressia.common.world.PacketSetLocalPlayer; import ru.windcorp.progressia.common.world.PacketWorldChange; -import ru.windcorp.progressia.common.world.entity.EntityData; // TODO refactor with no mercy public class DefaultClientCommsListener implements CommsListener { @@ -33,26 +30,12 @@ public class DefaultClientCommsListener implements CommsListener { } private void setLocalPlayer(PacketSetLocalPlayer packet) { - EntityData entity = getClient().getWorld().getData().getEntity( - 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) - )); + getClient().getLocalPlayer().setEntityId(packet.getEntityId()); } @Override public void onIOError(IOException reason) { + CrashReports.report(reason, "An IOException has occurred in communications"); // TODO implement } diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/world/LayerWorld.java b/src/main/java/ru/windcorp/progressia/client/graphics/world/LayerWorld.java index 0bbe753..94a5f16 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/world/LayerWorld.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/world/LayerWorld.java @@ -75,7 +75,9 @@ public class LayerWorld extends Layer { renderWorld(); } - if (client.getLocalPlayer() != null) { + client.getLocalPlayer().getEntity(); + + if (client.isReady()) { client.getLocalPlayer().update(client.getWorld()); } } @@ -139,10 +141,9 @@ public class LayerWorld extends Layer { private static final Renderable SELECTION_BOX = tmp_createSelectionBox(); private void tmp_drawSelectionBox() { - LocalPlayer player = client.getLocalPlayer(); - if (player == null) return; + if (!client.isReady()) return; - Vec3i selection = player.getSelection().getBlock(); + Vec3i selection = client.getLocalPlayer().getSelection().getBlock(); if (selection == null) return; helper.pushTransform().translate(selection.x, selection.y, selection.z); diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/world/LocalPlayer.java b/src/main/java/ru/windcorp/progressia/client/graphics/world/LocalPlayer.java index f0b4104..c877973 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/world/LocalPlayer.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/world/LocalPlayer.java @@ -1,16 +1,59 @@ 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.entity.EntityRenderable; -import ru.windcorp.progressia.common.world.PlayerData; 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(); - public LocalPlayer(EntityData entity) { - super(entity); + public LocalPlayer(Client client) { + 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() { diff --git a/src/main/java/ru/windcorp/progressia/client/world/entity/NPedModel.java b/src/main/java/ru/windcorp/progressia/client/world/entity/NPedModel.java index 43d9433..4c5397a 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/entity/NPedModel.java +++ b/src/main/java/ru/windcorp/progressia/client/world/entity/NPedModel.java @@ -116,6 +116,8 @@ public abstract class NPedModel extends EntityRenderable { this.body = body; this.head = head; this.scale = scale; + + evaluateAngles(); } @Override diff --git a/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java b/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java index ed5e5d6..ff9fc71 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java +++ b/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java @@ -23,7 +23,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.List; import java.util.Objects; import java.util.function.BiConsumer; 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.world.block.BlockData; 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.tile.TileData; import ru.windcorp.progressia.common.world.tile.TileDataStack; @@ -61,9 +59,6 @@ implements GenericChunk< BLOCK_FACE_COUNT ]; - private final List entities = - Collections.synchronizedList(new ArrayList<>()); - private final Collection listeners = Collections.synchronizedCollection(new ArrayList<>()); @@ -160,10 +155,6 @@ implements GenericChunk< face.getId(); } - public List getEntities() { - return entities; - } - private static void checkLocalCoordinates(Vec3i posInChunk) { if (!isInBounds(posInChunk)) { throw new IllegalCoordinatesException( @@ -219,10 +210,6 @@ implements GenericChunk< forEachTileStack(stack -> stack.forEach(tileData -> action.accept(stack, tileData))); } - public void forEachEntity(Consumer action) { - getEntities().forEach(action); - } - public WorldData getWorld() { return world; } diff --git a/src/main/java/ru/windcorp/progressia/common/world/WorldData.java b/src/main/java/ru/windcorp/progressia/common/world/WorldData.java index d96eaca..e031e0f 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/WorldData.java +++ b/src/main/java/ru/windcorp/progressia/common/world/WorldData.java @@ -20,11 +20,12 @@ package ru.windcorp.progressia.common.world; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; - +import java.util.Objects; 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.hash.TLongObjectHashMap; +import gnu.trove.set.TLongSet; import ru.windcorp.progressia.common.collision.CollisionModel; import ru.windcorp.progressia.common.world.block.BlockData; import ru.windcorp.progressia.common.world.entity.EntityData; @@ -46,14 +47,14 @@ implements GenericWorld< > { private final ChunkMap chunksByPos = new LongBasedChunkMap<>( - new TSynchronizedLongObjectMap<>(new TLongObjectHashMap<>(), this) + TCollections.synchronizedMap(new TLongObjectHashMap<>()) ); private final Collection chunks = Collections.unmodifiableCollection(chunksByPos.values()); private final TLongObjectMap entitiesById = - new TSynchronizedLongObjectMap<>(new TLongObjectHashMap<>(), this); + TCollections.synchronizedMap(new TLongObjectHashMap<>()); private final Collection entities = Collections.unmodifiableCollection(entitiesById.valueCollection()); @@ -86,6 +87,10 @@ implements GenericWorld< return entities; } + public TLongSet getLoadedEntities() { + return entitiesById.keySet(); + } + public void tmp_generate() { final int size = 1; Vec3i cursor = new Vec3i(0, 0, 0); @@ -118,10 +123,6 @@ implements GenericWorld< chunksByPos.put(chunk, chunk); - chunk.forEachEntity(entity -> - entitiesById.put(entity.getEntityId(), entity) - ); - chunk.onLoaded(); getListeners().forEach(l -> l.onChunkLoaded(this, chunk)); } @@ -130,10 +131,6 @@ implements GenericWorld< getListeners().forEach(l -> l.beforeChunkUnloaded(this, chunk)); chunk.beforeUnloaded(); - chunk.forEachEntity(entity -> - entitiesById.remove(entity.getEntityId()) - ); - chunksByPos.remove(chunk); } @@ -153,6 +150,45 @@ implements GenericWorld< 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() { return time; } diff --git a/src/main/java/ru/windcorp/progressia/common/world/WorldDataListener.java b/src/main/java/ru/windcorp/progressia/common/world/WorldDataListener.java index 12b3b9f..19469fa 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/WorldDataListener.java +++ b/src/main/java/ru/windcorp/progressia/common/world/WorldDataListener.java @@ -3,6 +3,7 @@ package ru.windcorp.progressia.common.world; import java.util.function.Consumer; import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.world.entity.EntityData; public interface WorldDataListener { @@ -30,5 +31,19 @@ public interface WorldDataListener { * @param chunk the chunk that is going to be unloaded */ 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) {} } diff --git a/src/main/java/ru/windcorp/progressia/common/world/entity/EntityData.java b/src/main/java/ru/windcorp/progressia/common/world/entity/EntityData.java index af3da1e..cea74b7 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/entity/EntityData.java +++ b/src/main/java/ru/windcorp/progressia/common/world/entity/EntityData.java @@ -2,6 +2,7 @@ package ru.windcorp.progressia.common.world.entity; import glm.vec._2.Vec2; import glm.vec._3.Vec3; +import ru.windcorp.jputil.chars.StringUtil; import ru.windcorp.progressia.common.collision.Collideable; import ru.windcorp.progressia.common.collision.CollisionModel; 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(); + /** + * 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 CollisionModel collisionModel = null; @@ -69,6 +76,9 @@ public class EntityData extends StatefulObject implements Collideable, GenericEn } 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; } @@ -127,5 +137,18 @@ public class EntityData extends StatefulObject implements Collideable, GenericEn 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)); + } } diff --git a/src/main/java/ru/windcorp/progressia/common/world/entity/PacketRevokeEntity.java b/src/main/java/ru/windcorp/progressia/common/world/entity/PacketRevokeEntity.java new file mode 100644 index 0000000..03ec081 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/world/entity/PacketRevokeEntity.java @@ -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); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkSets.java b/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkSets.java index ba959cc..a8b23d9 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkSets.java +++ b/src/main/java/ru/windcorp/progressia/common/world/generic/ChunkSets.java @@ -1,5 +1,9 @@ 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.set.hash.TLongHashSet; @@ -17,6 +21,78 @@ public class ChunkSets { return new LongBasedChunkSet(new TSynchronizedLongSet(new TLongHashSet())); } + private final static ChunkSet EMPTY_SET = new ChunkSet() { + + @Override + public Iterator iterator() { + return new Iterator() { + @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() {} } diff --git a/src/main/java/ru/windcorp/progressia/server/ChunkManager.java b/src/main/java/ru/windcorp/progressia/server/ChunkManager.java index 10a1c38..9220827 100644 --- a/src/main/java/ru/windcorp/progressia/server/ChunkManager.java +++ b/src/main/java/ru/windcorp/progressia/server/ChunkManager.java @@ -8,10 +8,11 @@ import org.apache.logging.log4j.LogManager; import glm.vec._3.i.Vec3i; 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.generic.ChunkSet; import ru.windcorp.progressia.common.world.generic.ChunkSets; -import ru.windcorp.progressia.test.TestChunkSender; import ru.windcorp.progressia.test.TestContent; public class ChunkManager { @@ -148,14 +149,29 @@ public class ChunkManager { public void sendChunk(Player player, Vec3i chunkPos) { 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); } public void revokeChunk(Player player, Vec3i chunkPos) { 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); if (vision != null) { @@ -172,6 +188,16 @@ public class ChunkManager { 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() { return server; diff --git a/src/main/java/ru/windcorp/progressia/server/EntityManager.java b/src/main/java/ru/windcorp/progressia/server/EntityManager.java new file mode 100644 index 0000000..3f810cf --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/EntityManager.java @@ -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 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; + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/PacketSendEntity.java b/src/main/java/ru/windcorp/progressia/server/PacketSendEntity.java new file mode 100644 index 0000000..d1da490 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/PacketSendEntity.java @@ -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); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/server/Player.java b/src/main/java/ru/windcorp/progressia/server/Player.java index 2e4da82..a20a49e 100644 --- a/src/main/java/ru/windcorp/progressia/server/Player.java +++ b/src/main/java/ru/windcorp/progressia/server/Player.java @@ -54,5 +54,9 @@ public class Player extends PlayerData implements ChunkLoader { } } } + + public String getLogin() { + return getClient().getLogin(); + } } diff --git a/src/main/java/ru/windcorp/progressia/server/PlayerManager.java b/src/main/java/ru/windcorp/progressia/server/PlayerManager.java index b1a6425..4a4bb65 100644 --- a/src/main/java/ru/windcorp/progressia/server/PlayerManager.java +++ b/src/main/java/ru/windcorp/progressia/server/PlayerManager.java @@ -4,10 +4,13 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import glm.vec._2.Vec2; +import glm.vec._3.Vec3; import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.common.util.Vectors; import ru.windcorp.progressia.common.util.crash.CrashReports; import ru.windcorp.progressia.common.world.entity.EntityData; +import ru.windcorp.progressia.common.world.entity.EntityDataRegistry; import ru.windcorp.progressia.test.TestContent; public class PlayerManager { @@ -38,13 +41,28 @@ public class PlayerManager { getServer().getChunkManager().loadChunk(chunkPos); } - return getServer().getWorld().getData().getEntity(TestContent.PLAYER_ENTITY_ID); + EntityData entity = spawnPlayerEntity(login); + return entity; } else { CrashReports.report(null, "Unknown login %s, javahorse stupid", login); 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() { return server; } diff --git a/src/main/java/ru/windcorp/progressia/server/Server.java b/src/main/java/ru/windcorp/progressia/server/Server.java index 158639a..d2459c7 100644 --- a/src/main/java/ru/windcorp/progressia/server/Server.java +++ b/src/main/java/ru/windcorp/progressia/server/Server.java @@ -32,6 +32,7 @@ public class Server { private final ClientManager clientManager; private final PlayerManager playerManager; private final ChunkManager chunkManager; + private final EntityManager entityManager; private final TaskQueue taskQueue = new TaskQueue(this::isServerThread); @@ -44,9 +45,11 @@ public class Server { this.clientManager = new ClientManager(this); this.playerManager = new PlayerManager(this); this.chunkManager = new ChunkManager(this); + this.entityManager = new EntityManager(this); - schedule(this::scheduleChunkTicks); + schedule(this::scheduleWorldTicks); schedule(chunkManager::tick); + schedule(entityManager::tick); } /** @@ -197,8 +200,9 @@ public class Server { serverThread.stop(); } - private void scheduleChunkTicks(Server server) { + private void scheduleWorldTicks(Server server) { server.getWorld().getChunks().forEach(chunk -> requestEvaluation(chunk.getTickTask())); + requestEvaluation(server.getWorld().getTickEntitiesTask()); } /** diff --git a/src/main/java/ru/windcorp/progressia/server/ServerState.java b/src/main/java/ru/windcorp/progressia/server/ServerState.java index aa19807..1c18a37 100644 --- a/src/main/java/ru/windcorp/progressia/server/ServerState.java +++ b/src/main/java/ru/windcorp/progressia/server/ServerState.java @@ -16,7 +16,7 @@ public class ServerState { public static void startServer() { Server server = new Server(new WorldData()); - server.getWorld().getData().tmp_generate(); +// server.getWorld().getData().tmp_generate(); setInstance(server); server.start(); } diff --git a/src/main/java/ru/windcorp/progressia/server/comms/ClientManager.java b/src/main/java/ru/windcorp/progressia/server/comms/ClientManager.java index 65b3221..c82a582 100644 --- a/src/main/java/ru/windcorp/progressia/server/comms/ClientManager.java +++ b/src/main/java/ru/windcorp/progressia/server/comms/ClientManager.java @@ -4,6 +4,8 @@ import java.util.Collection; import java.util.Collections; import java.util.concurrent.atomic.AtomicInteger; +import org.apache.logging.log4j.LogManager; + import glm.vec._3.i.Vec3i; import gnu.trove.TCollections; import gnu.trove.map.TIntObjectMap; @@ -41,10 +43,10 @@ public class ClientManager { if (client instanceof ClientChat) { addClientChat((ClientChat) client); - } - - if (client instanceof ClientPlayer) { - addClientPlayer((ClientPlayer) client); + + if (client instanceof ClientPlayer) { + addClientPlayer((ClientPlayer) client); + } } client.addListener(new DefaultServerCommsListener(this, client)); @@ -58,18 +60,12 @@ public class ClientManager { private void addClientPlayer(ClientPlayer client) { String login = client.getLogin(); - EntityData entity; - synchronized (getServer().getWorld().getData()) { - entity = getServer().getPlayerManager().conjurePlayerEntity(login); - - Player player = new Player(entity, getServer(), client); - - getServer().getPlayerManager().getPlayers().add(player); - - getServer().getChunkManager().sendChunk(player, entity.getChunkCoords(null)); - } + EntityData entity = getServer().getPlayerManager().conjurePlayerEntity(login); + Player player = new Player(entity, getServer(), client); + getServer().getPlayerManager().getPlayers().add(player); PacketSetLocalPlayer packet = new PacketSetLocalPlayer(); + LogManager.getLogger().info("Sending local player ID {}", EntityData.formatEntityId(entity.getEntityId())); packet.set(entity.getEntityId()); client.sendPacket(packet); } diff --git a/src/main/java/ru/windcorp/progressia/server/comms/ClientPlayer.java b/src/main/java/ru/windcorp/progressia/server/comms/ClientPlayer.java index bf7ac28..00db77c 100644 --- a/src/main/java/ru/windcorp/progressia/server/comms/ClientPlayer.java +++ b/src/main/java/ru/windcorp/progressia/server/comms/ClientPlayer.java @@ -1,6 +1,8 @@ package ru.windcorp.progressia.server.comms; 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; public abstract class ClientPlayer extends ClientChat { @@ -26,6 +28,11 @@ public abstract class ClientPlayer extends ClientChat { 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) { return true; } diff --git a/src/main/java/ru/windcorp/progressia/server/world/ChunkLogic.java b/src/main/java/ru/windcorp/progressia/server/world/ChunkLogic.java index b8b1779..622ca00 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/ChunkLogic.java +++ b/src/main/java/ru/windcorp/progressia/server/world/ChunkLogic.java @@ -11,15 +11,12 @@ import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.common.world.ChunkData; import ru.windcorp.progressia.common.world.Coordinates; 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.tile.TileDataStack; import ru.windcorp.progressia.common.world.tile.TileReference; import ru.windcorp.progressia.server.world.block.BlockLogic; import ru.windcorp.progressia.server.world.block.BlockLogicRegistry; 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.ticking.TickingPolicy; import ru.windcorp.progressia.server.world.tile.TickableTile; @@ -112,15 +109,6 @@ public class ChunkLogic implements GenericChunk< }); } - public void forEachEntity(BiConsumer action) { - getData().forEachEntity(data -> { - action.accept( - EntityLogicRegistry.getInstance().get(data.getId()), - data - ); - }); - } - public TickChunk getTickTask() { return tickTask; } diff --git a/src/main/java/ru/windcorp/progressia/server/world/WorldLogic.java b/src/main/java/ru/windcorp/progressia/server/world/WorldLogic.java index 00aae35..e5ec697 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/WorldLogic.java +++ b/src/main/java/ru/windcorp/progressia/server/world/WorldLogic.java @@ -13,6 +13,8 @@ import ru.windcorp.progressia.common.world.entity.EntityData; import ru.windcorp.progressia.common.world.generic.GenericWorld; import ru.windcorp.progressia.server.Server; 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.TileLogicStack; @@ -30,6 +32,8 @@ implements GenericWorld< private final Map chunks = new HashMap<>(); + private final Evaluation tickEntitiesTask = new TickEntitiesTask(); + public WorldLogic(WorldData data, Server server) { this.data = data; this.server = server; @@ -64,6 +68,10 @@ implements GenericWorld< return getData().getEntities(); } + public Evaluation getTickEntitiesTask() { + return tickEntitiesTask; + } + public Server getServer() { return server; } diff --git a/src/main/java/ru/windcorp/progressia/server/world/tasks/TickChunk.java b/src/main/java/ru/windcorp/progressia/server/world/tasks/TickChunk.java index 76570b5..631e5a5 100644 --- a/src/main/java/ru/windcorp/progressia/server/world/tasks/TickChunk.java +++ b/src/main/java/ru/windcorp/progressia/server/world/tasks/TickChunk.java @@ -14,7 +14,6 @@ import ru.windcorp.progressia.common.world.block.BlockFace; import ru.windcorp.progressia.common.world.tile.TileDataStack; import ru.windcorp.progressia.server.Server; 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.block.BlockLogic; import ru.windcorp.progressia.server.world.block.TickableBlock; @@ -55,7 +54,6 @@ public class TickChunk extends Evaluation { public void evaluate(Server server) { tickRegulars(server); tickRandom(server); - tickEntities(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 public void getRelevantChunk(Vec3i output) { Vec3i p = chunk.getData().getPosition(); diff --git a/src/main/java/ru/windcorp/progressia/server/world/tasks/TickEntitiesTask.java b/src/main/java/ru/windcorp/progressia/server/world/tasks/TickEntitiesTask.java new file mode 100644 index 0000000..da964bd --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/server/world/tasks/TickEntitiesTask.java @@ -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; + } + +} diff --git a/src/main/java/ru/windcorp/progressia/test/TestChunkSender.java b/src/main/java/ru/windcorp/progressia/test/TestChunkSender.java deleted file mode 100644 index 815fb6d..0000000 --- a/src/main/java/ru/windcorp/progressia/test/TestChunkSender.java +++ /dev/null @@ -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); - } - -} diff --git a/src/main/java/ru/windcorp/progressia/test/TestContent.java b/src/main/java/ru/windcorp/progressia/test/TestContent.java index 517df5f..ee6bedb 100644 --- a/src/main/java/ru/windcorp/progressia/test/TestContent.java +++ b/src/main/java/ru/windcorp/progressia/test/TestContent.java @@ -7,16 +7,12 @@ import java.util.function.Consumer; 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 ru.windcorp.progressia.client.ClientState; import ru.windcorp.progressia.client.audio.SoundEffect; import ru.windcorp.progressia.client.comms.controls.*; import ru.windcorp.progressia.client.graphics.input.KeyEvent; 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.world.block.*; 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.io.ChunkIO; 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.Coordinates; import ru.windcorp.progressia.common.world.block.*; @@ -214,12 +209,9 @@ public class TestContent { private static Selection getSelection() { ru.windcorp.progressia.client.Client client = ClientState.getInstance(); - if (client == null) return null; + if (client == null || !client.isReady()) return null; - LocalPlayer player = client.getLocalPlayer(); - if (player == null) return null; - - return player.getSelection(); + return client.getLocalPlayer().getSelection(); } 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() { diff --git a/src/main/java/ru/windcorp/progressia/test/TestPlayerControls.java b/src/main/java/ru/windcorp/progressia/test/TestPlayerControls.java index 1c67c7c..d00ca09 100644 --- a/src/main/java/ru/windcorp/progressia/test/TestPlayerControls.java +++ b/src/main/java/ru/windcorp/progressia/test/TestPlayerControls.java @@ -61,7 +61,7 @@ public class TestPlayerControls { private Runnable updateCallback = null; public void applyPlayerControls() { - if (ClientState.getInstance() == null || ClientState.getInstance().getLocalPlayer() == null) { + if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) { return; } @@ -107,7 +107,7 @@ public class TestPlayerControls { } public void handleInput(Input input) { - if (ClientState.getInstance() == null || ClientState.getInstance().getLocalPlayer() == null) { + if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) { return; }