Updated Upstream (Paper & Tuinity)

Upstream has released updates that appear to apply and compile correctly

Paper Changes:
0aa3cc7228 Suppress deprecation registration warnings for brigadier events (#6297)
81a7559b41 Correct handling of invalid maps (#6302)

Tuinity Changes:
5620c57206 Use Velocity compression and cipher natives (#351)
3be4fc5809 [CI-SKIP] Redirect to CI when clicking CI badge in README (#344)
This commit is contained in:
William Blake Galbreath
2021-07-31 22:50:13 -05:00
parent a2340e3381
commit ef304d16da
12 changed files with 381 additions and 49 deletions

View File

@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
diff --git a/build.gradle.kts b/build.gradle.kts
index b50463c2356301a1b47a0bf4f50dc1f121d363a1..d658b7502185f1f7c938d510e2f8404fdaa66bb6 100644
index b50463c2356301a1b47a0bf4f50dc1f121d363a1..66f5e6edc2bac290664c534df213058eaeab3b4e 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,10 +1,16 @@
@@ -37,7 +37,11 @@ index b50463c2356301a1b47a0bf4f50dc1f121d363a1..d658b7502185f1f7c938d510e2f8404f
import shadow.org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor.PLUGIN_CACHE_FILE
import java.nio.file.Files
import java.util.Locale
@@ -28,8 +34,8 @@ repositories {
@@ -25,11 +31,12 @@ repositories {
}
}
// Paper end
+ maven("https://repo.velocitypowered.com/snapshots/") // Tuinity
}
dependencies {
@@ -48,7 +52,15 @@ index b50463c2356301a1b47a0bf4f50dc1f121d363a1..d658b7502185f1f7c938d510e2f8404f
// Paper start
implementation("org.jline:jline-terminal-jansi:3.12.1")
implementation("net.minecrell:terminalconsoleappender:1.2.0")
@@ -80,7 +86,7 @@ tasks.jar {
@@ -62,6 +69,7 @@ dependencies {
implementation("io.netty:netty-all:4.1.65.Final") // Paper
implementation("org.quiltmc:tiny-mappings-parser:0.3.0") // Paper - needed to read mappings for stacktrace deobfuscation
+ implementation("com.velocitypowered:velocity-native:1.1.0-SNAPSHOT") // Tuinity
testImplementation("io.github.classgraph:classgraph:4.8.47") // Paper - mob goal test
testImplementation("junit:junit:4.13.1")
@@ -80,7 +88,7 @@ tasks.jar {
attributes(
"Main-Class" to "org.bukkit.craftbukkit.Main",
"Implementation-Title" to "CraftBukkit",
@@ -57,7 +69,7 @@ index b50463c2356301a1b47a0bf4f50dc1f121d363a1..d658b7502185f1f7c938d510e2f8404f
"Implementation-Vendor" to date, // Paper
"Specification-Title" to "Bukkit",
"Specification-Version" to project.version,
@@ -105,6 +111,22 @@ publishing {
@@ -105,6 +113,22 @@ publishing {
}
}
@@ -80,7 +92,7 @@ index b50463c2356301a1b47a0bf4f50dc1f121d363a1..d658b7502185f1f7c938d510e2f8404f
val generatePom = tasks.named<GenerateMavenPom>("generatePomFileForMavenPublication")
tasks.shadowJar {
@@ -176,7 +198,7 @@ tasks.test {
@@ -176,7 +200,7 @@ tasks.test {
fun TaskContainer.registerRunTask(
name: String, block: JavaExec.() -> Unit
): TaskProvider<JavaExec> = register<JavaExec>(name) {
@@ -29988,8 +30000,256 @@ index 5e09890ba2fe326503a49b2dbec09845f5c8c5eb..3ad3652f8074de10222fb01c50548b43
this.z = z;
return this;
}
diff --git a/src/main/java/net/minecraft/network/CipherDecoder.java b/src/main/java/net/minecraft/network/CipherDecoder.java
index 06d545bc7206dd0d56cf27c31935c0f5ed21ef08..3dfbe08b68b958a52d5f4464b22b70f3ad9a012c 100644
--- a/src/main/java/net/minecraft/network/CipherDecoder.java
+++ b/src/main/java/net/minecraft/network/CipherDecoder.java
@@ -7,14 +7,30 @@ import java.util.List;
import javax.crypto.Cipher;
public class CipherDecoder extends MessageToMessageDecoder<ByteBuf> {
- private final CipherBase cipher;
+ private final com.velocitypowered.natives.encryption.VelocityCipher cipher; // Tuinity
- public CipherDecoder(Cipher cipher) {
- this.cipher = new CipherBase(cipher);
+ public CipherDecoder(com.velocitypowered.natives.encryption.VelocityCipher cipher) { // Tuinity
+ this.cipher = cipher; // Tuinity
}
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
- list.add(this.cipher.decipher(channelHandlerContext, byteBuf));
+ // Tuinity start
+ ByteBuf compatible = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), cipher, byteBuf);
+ try {
+ cipher.process(compatible);
+ list.add(compatible);
+ } catch (Exception e) {
+ compatible.release(); // compatible will never be used if we throw an exception
+ throw e;
+ }
+ // Tuinity end
}
+
+ // Tuinity start
+ @Override
+ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
+ cipher.close();
+ }
+ // Tuinity end
}
diff --git a/src/main/java/net/minecraft/network/CipherEncoder.java b/src/main/java/net/minecraft/network/CipherEncoder.java
index 50a7058b18a8ca05363b73eaefbd812ef50d53f1..34bd72ebace9a61625693d724ea0a88c0dd1f601 100644
--- a/src/main/java/net/minecraft/network/CipherEncoder.java
+++ b/src/main/java/net/minecraft/network/CipherEncoder.java
@@ -4,16 +4,33 @@ import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import javax.crypto.Cipher;
+import java.util.List;
-public class CipherEncoder extends MessageToByteEncoder<ByteBuf> {
- private final CipherBase cipher;
+public class CipherEncoder extends io.netty.handler.codec.MessageToMessageEncoder<ByteBuf> { // Tuinity - change superclass
+ private final com.velocitypowered.natives.encryption.VelocityCipher cipher; // Tuinity
- public CipherEncoder(Cipher cipher) {
- this.cipher = new CipherBase(cipher);
+ public CipherEncoder(com.velocitypowered.natives.encryption.VelocityCipher cipher) { // Tuinity
+ this.cipher = cipher; // Tuinity
}
@Override
- protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, ByteBuf byteBuf2) throws Exception {
- this.cipher.encipher(byteBuf, byteBuf2);
+ protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
+ // Tuinity start
+ ByteBuf compatible = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), cipher, byteBuf);
+ try {
+ cipher.process(compatible);
+ list.add(compatible);
+ } catch (Exception e) {
+ compatible.release(); // compatible will never be used if we throw an exception
+ throw e;
+ }
+ // Tuinity end
}
+
+ // Tuinity start
+ @Override
+ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
+ cipher.close();
+ }
+ // Tuinity end
}
diff --git a/src/main/java/net/minecraft/network/CompressionDecoder.java b/src/main/java/net/minecraft/network/CompressionDecoder.java
index efd05c8c1114aab4c237ccbc2e4e935a08c076ee..c18e9773b707fa64d2ea0985c811174c4d82ccbd 100644
--- a/src/main/java/net/minecraft/network/CompressionDecoder.java
+++ b/src/main/java/net/minecraft/network/CompressionDecoder.java
@@ -11,14 +11,18 @@ import java.util.zip.Inflater;
public class CompressionDecoder extends ByteToMessageDecoder {
public static final int MAXIMUM_COMPRESSED_LENGTH = 2097152;
public static final int MAXIMUM_UNCOMPRESSED_LENGTH = 8388608;
- private final Inflater inflater;
+ // Tuinity start
+ // private final Inflater inflater;
+ private final com.velocitypowered.natives.compression.VelocityCompressor compressor;
+ // Tuinity end
private int threshold;
private boolean validateDecompressed;
- public CompressionDecoder(int compressionThreshold, boolean bl) {
+ public CompressionDecoder(com.velocitypowered.natives.compression.VelocityCompressor compressor, int compressionThreshold, boolean bl) {
this.threshold = compressionThreshold;
this.validateDecompressed = bl;
- this.inflater = new Inflater();
+ this.compressor = compressor; // Tuinity
+ // this.inflater = new Inflater(); // Tuinity
}
@Override
@@ -39,17 +43,39 @@ public class CompressionDecoder extends ByteToMessageDecoder {
}
}
- byte[] bs = new byte[friendlyByteBuf.readableBytes()];
- friendlyByteBuf.readBytes(bs);
- this.inflater.setInput(bs);
- byte[] cs = new byte[i];
- this.inflater.inflate(cs);
- list.add(Unpooled.wrappedBuffer(cs));
- this.inflater.reset();
+ // Tuinity start
+// byte[] bs = new byte[friendlyByteBuf.readableBytes()];
+// friendlyByteBuf.readBytes(bs);
+// this.inflater.setInput(bs);
+// byte[] cs = new byte[i];
+// this.inflater.inflate(cs);
+// list.add(Unpooled.wrappedBuffer(cs));
+// this.inflater.reset();
+ int claimedUncompressedSize = i; // OBFHELPER
+ ByteBuf compatibleIn = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), compressor, byteBuf);
+ ByteBuf uncompressed = com.velocitypowered.natives.util.MoreByteBufUtils.preferredBuffer(channelHandlerContext.alloc(), compressor, claimedUncompressedSize);
+ try {
+ compressor.inflate(compatibleIn, uncompressed, claimedUncompressedSize);
+ list.add(uncompressed);
+ byteBuf.clear();
+ } catch (Exception e) {
+ uncompressed.release();
+ throw e;
+ } finally {
+ compatibleIn.release();
+ }
+ // Tuinity end
}
}
}
+ // Tuinity start
+ @Override
+ public void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
+ compressor.close();
+ }
+ // Tuinity end
+
public void setThreshold(int compressionThreshold, boolean bl) {
this.threshold = compressionThreshold;
this.validateDecompressed = bl;
diff --git a/src/main/java/net/minecraft/network/CompressionEncoder.java b/src/main/java/net/minecraft/network/CompressionEncoder.java
index 524c0c674f63cfcb601416a18348f37aabb4e3ff..241ee2247d6f0ead736e5e25f2bb25655d9b3d19 100644
--- a/src/main/java/net/minecraft/network/CompressionEncoder.java
+++ b/src/main/java/net/minecraft/network/CompressionEncoder.java
@@ -6,39 +6,71 @@ import io.netty.handler.codec.MessageToByteEncoder;
import java.util.zip.Deflater;
public class CompressionEncoder extends MessageToByteEncoder<ByteBuf> {
- private final byte[] encodeBuf = new byte[8192];
- private final Deflater deflater;
+ // private final byte[] encodeBuf = new byte[8192]; // Tuinity
+ // private final Deflater deflater; // Tuinity
+ private final com.velocitypowered.natives.compression.VelocityCompressor compressor; // Tuinity
private int threshold;
- public CompressionEncoder(int compressionThreshold) {
+ public CompressionEncoder(com.velocitypowered.natives.compression.VelocityCompressor compressor, int compressionThreshold) {
this.threshold = compressionThreshold;
- this.deflater = new Deflater();
+ // this.deflater = new Deflater(); // Tuinity
+ this.compressor = compressor; // Tuinity
}
@Override
- protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, ByteBuf byteBuf2) {
+ protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, ByteBuf byteBuf2) throws Exception { // Tuinity
int i = byteBuf.readableBytes();
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(byteBuf2);
if (i < this.threshold) {
friendlyByteBuf.writeVarInt(0);
friendlyByteBuf.writeBytes(byteBuf);
} else {
- byte[] bs = new byte[i];
- byteBuf.readBytes(bs);
- friendlyByteBuf.writeVarInt(bs.length);
- this.deflater.setInput(bs, 0, i);
- this.deflater.finish();
-
- while(!this.deflater.finished()) {
- int j = this.deflater.deflate(this.encodeBuf);
- friendlyByteBuf.writeBytes(this.encodeBuf, 0, j);
+ // Tuinity start
+// byte[] bs = new byte[i];
+// byteBuf.readBytes(bs);
+// friendlyByteBuf.writeVarInt(bs.length);
+// this.deflater.setInput(bs, 0, i);
+// this.deflater.finish();
+//
+// while(!this.deflater.finished()) {
+// int j = this.deflater.deflate(this.encodeBuf);
+// friendlyByteBuf.writeBytes(this.encodeBuf, 0, j);
+// }
+//
+// this.deflater.reset();
+ friendlyByteBuf.writeVarInt(i);
+ ByteBuf compatibleIn = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), compressor, byteBuf);
+ try {
+ compressor.deflate(compatibleIn, byteBuf2);
+ } finally {
+ compatibleIn.release();
}
-
- this.deflater.reset();
+ // Tuinity end
}
}
+ // Tuinity start
+ @Override
+ protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) {
+ // We allocate bytes to be compressed plus 1 byte. This covers two cases:
+ //
+ // - Compression
+ // According to https://github.com/ebiggers/libdeflate/blob/master/libdeflate.h#L103,
+ // if the data compresses well (and we do not have some pathological case) then the maximum
+ // size the compressed size will ever be is the input size minus one.
+ // - Uncompressed
+ // This is fairly obvious - we will then have one more than the uncompressed size.
+ int initialBufferSize = msg.readableBytes() + 1;
+ return com.velocitypowered.natives.util.MoreByteBufUtils.preferredBuffer(ctx.alloc(), compressor, initialBufferSize);
+ }
+
+ @Override
+ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
+ compressor.close();
+ }
+ // Tuinity end
+
public int getThreshold() {
return this.threshold;
}
diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java
index 9d09ec3b127e3440bef6b248578dec109407f9ff..4b6bbdbdf581b8a751c08708ee24e8b2a85534a0 100644
index 9d09ec3b127e3440bef6b248578dec109407f9ff..a8a46be5ee2173c2d1c7ad7299f05ab5ce3390b5 100644
--- a/src/main/java/net/minecraft/network/Connection.java
+++ b/src/main/java/net/minecraft/network/Connection.java
@@ -49,6 +49,8 @@ import org.apache.logging.log4j.Logger;
@@ -30254,6 +30514,59 @@ index 9d09ec3b127e3440bef6b248578dec109407f9ff..4b6bbdbdf581b8a751c08708ee24e8b2
}
if (!this.isConnected() && !this.disconnectionHandled) {
@@ -498,11 +657,28 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
return networkmanager;
}
- public void setEncryptionKey(Cipher decryptionCipher, Cipher encryptionCipher) {
- this.encrypted = true;
- this.channel.pipeline().addBefore("splitter", "decrypt", new CipherDecoder(decryptionCipher));
- this.channel.pipeline().addBefore("prepender", "encrypt", new CipherEncoder(encryptionCipher));
+ // Tuinity start
+// public void setEncryptionKey(Cipher decryptionCipher, Cipher encryptionCipher) {
+// this.encrypted = true;
+// this.channel.pipeline().addBefore("splitter", "decrypt", new CipherDecoder(decryptionCipher));
+// this.channel.pipeline().addBefore("prepender", "encrypt", new CipherEncoder(encryptionCipher));
+// }
+
+ public void setupEncryption(javax.crypto.SecretKey key) throws net.minecraft.util.CryptException {
+ if (!this.encrypted) {
+ try {
+ com.velocitypowered.natives.encryption.VelocityCipher decryption = com.velocitypowered.natives.util.Natives.cipher.get().forDecryption(key);
+ com.velocitypowered.natives.encryption.VelocityCipher encryption = com.velocitypowered.natives.util.Natives.cipher.get().forEncryption(key);
+
+ this.encrypted = true;
+ this.channel.pipeline().addBefore("splitter", "decrypt", new CipherDecoder(decryption));
+ this.channel.pipeline().addBefore("prepender", "encrypt", new CipherEncoder(encryption));
+ } catch (java.security.GeneralSecurityException e) {
+ throw new net.minecraft.util.CryptException(e);
+ }
+ }
}
+ // Tuinity end
public boolean isEncrypted() {
return this.encrypted;
@@ -531,16 +707,17 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
public void setupCompression(int compressionThreshold, boolean flag) {
if (compressionThreshold >= 0) {
+ com.velocitypowered.natives.compression.VelocityCompressor compressor = com.velocitypowered.natives.util.Natives.compress.get().create(-1); // Tuinity
if (this.channel.pipeline().get("decompress") instanceof CompressionDecoder) {
((CompressionDecoder) this.channel.pipeline().get("decompress")).setThreshold(compressionThreshold, flag);
} else {
- this.channel.pipeline().addBefore("decoder", "decompress", new CompressionDecoder(compressionThreshold, flag));
+ this.channel.pipeline().addBefore("decoder", "decompress", new CompressionDecoder(compressor, compressionThreshold, flag)); // Tuinity
}
if (this.channel.pipeline().get("compress") instanceof CompressionEncoder) {
((CompressionEncoder) this.channel.pipeline().get("compress")).setThreshold(compressionThreshold);
} else {
- this.channel.pipeline().addBefore("encoder", "compress", new CompressionEncoder(compressionThreshold));
+ this.channel.pipeline().addBefore("encoder", "compress", new CompressionEncoder(compressor, compressionThreshold)); // Tuinity
}
} else {
if (this.channel.pipeline().get("decompress") instanceof CompressionDecoder) {
diff --git a/src/main/java/net/minecraft/network/protocol/PacketUtils.java b/src/main/java/net/minecraft/network/protocol/PacketUtils.java
index bcf53ec07b8eeec7a88fb67e6fb908362e6f51b0..7265bee436d61d33645fa2d9ed4240529834dbf5 100644
--- a/src/main/java/net/minecraft/network/protocol/PacketUtils.java
@@ -33486,6 +33799,22 @@ index 0f6b534a4c789a2f09f6c4624e5d58b99c7ed0e6..fea852674098fe411841d8e5ebeace7d
public WorldGenRegion(ServerLevel world, List<ChunkAccess> list, ChunkStatus chunkstatus, int i) {
this.generatingStatus = chunkstatus;
this.writeRadiusCutoff = i;
diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
index 961660f6f9e00b93252519e38b74c66c53388ed2..c80280150897064dc9d814edfbbcc1ce6eb9cf52 100644
--- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
+++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
@@ -104,6 +104,11 @@ public class ServerConnectionListener {
ServerConnectionListener.LOGGER.info("Using default channel type");
}
+ // Tuinity start - indicate Velocity natives in use
+ ServerConnectionListener.LOGGER.info("Tuinity: Using " + com.velocitypowered.natives.util.Natives.compress.getLoadedVariant() + " compression from Velocity.");
+ ServerConnectionListener.LOGGER.info("Tuinity: Using " + com.velocitypowered.natives.util.Natives.cipher.getLoadedVariant() + " cipher from Velocity.");
+ // Tuinity end
+
this.channels.add(((ServerBootstrap) ((ServerBootstrap) (new ServerBootstrap()).channel(oclass)).childHandler(new ChannelInitializer<Channel>() {
protected void initChannel(Channel channel) {
try {
diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index 064aecb28f05fcf572ee7d29f611a31cc7b6e49a..c4cea533f619624976c4d1290312ed1a6b250855 100644
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -33683,6 +34012,28 @@ index 064aecb28f05fcf572ee7d29f611a31cc7b6e49a..c4cea533f619624976c4d1290312ed1a
private boolean isPlayerCollidingWithAnythingNew(LevelReader world, AABB box) {
Stream<VoxelShape> stream = world.getCollisions(this.player, this.player.getBoundingBox().deflate(9.999999747378752E-6D), (entity) -> {
return true;
diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
index 45e77d96f673ce68cf15ce3d45fd1eeffed4d8d8..9ab220ef0d20151d4e205f3edc213fd9353601ad 100644
--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
@@ -275,12 +275,14 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener
}
SecretKey secretkey = packet.getSecretKey(privatekey);
- Cipher cipher = Crypt.getCipher(2, secretkey);
- Cipher cipher1 = Crypt.getCipher(1, secretkey);
+ // Tuinity start
+// Cipher cipher = Crypt.getCipher(2, secretkey);
+// Cipher cipher1 = Crypt.getCipher(1, secretkey);
+ // Tuinity end
s = (new BigInteger(Crypt.digestData("", this.server.getKeyPair().getPublic(), secretkey))).toString(16);
this.state = ServerLoginPacketListenerImpl.State.AUTHENTICATING;
- this.connection.setEncryptionKey(cipher, cipher1);
+ this.connection.setupEncryption(secretkey); // Tuinity
} catch (CryptException cryptographyexception) {
throw new IllegalStateException("Protocol error", cryptographyexception);
}
diff --git a/src/main/java/net/minecraft/server/players/GameProfileCache.java b/src/main/java/net/minecraft/server/players/GameProfileCache.java
index 61405c2b53e03a4b83e2c70c6e4d3739ca9676cb..1f307f8e3f0f484dad33e9af085dabd93a3509fd 100644
--- a/src/main/java/net/minecraft/server/players/GameProfileCache.java
@@ -34822,7 +35173,7 @@ index 925f16d5eb092518ef774f69a8d99689feb0f5d7..01d8af06f19427354cac95d691e65d31
public BlockPos getHomePos() { // Paper - public
diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java
index 19980b2d627eb3cacf8d0c3e6785ad2206910fbc..e7a7de5ad9b64876df77e20465631ca8e5b19a4a 100644
index d8780f06efc261a42389e466573a0bcf10c333f6..9e0eec259fdae57e235bfe00ece4df9957edc642 100644
--- a/src/main/java/net/minecraft/world/entity/player/Player.java
+++ b/src/main/java/net/minecraft/world/entity/player/Player.java
@@ -498,6 +498,11 @@ public abstract class Player extends LivingEntity {