mirror of
https://github.com/Minestom/Minestom.git
synced 2024-12-28 12:07:42 +01:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
974372d2bd
2
.github/README.md
vendored
2
.github/README.md
vendored
@ -53,7 +53,7 @@ Minestom isn't perfect, our choices make it much better for some cases, worse fo
|
|||||||
|
|
||||||
## Disadvantages
|
## Disadvantages
|
||||||
* Does not work with Bukkit/Spigot plugins
|
* Does not work with Bukkit/Spigot plugins
|
||||||
* Does not work with older clients
|
* Does not work with older clients (using a proxy with ViaBackwards is possible)
|
||||||
* Bad for those who want a vanilla experience
|
* Bad for those who want a vanilla experience
|
||||||
* Longer to develop something playable
|
* Longer to develop something playable
|
||||||
* Multi-threaded environments are prone to complications
|
* Multi-threaded environments are prone to complications
|
||||||
|
27
.github/workflows/javadoc.yml
vendored
Normal file
27
.github/workflows/javadoc.yml
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
name: Build and deploy Javadoc
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up JDK 11
|
||||||
|
uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: 1.11
|
||||||
|
- name: Build javadoc
|
||||||
|
run: gradle javadoc
|
||||||
|
|
||||||
|
- name: Deploy javadoc to its assigned branch
|
||||||
|
uses: s0/git-publish-subdir-action@develop
|
||||||
|
env:
|
||||||
|
REPO: self
|
||||||
|
BRANCH: javadoc
|
||||||
|
FOLDER: docs
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
25
.github/workflows/tests.yml
vendored
Normal file
25
.github/workflows/tests.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
name: Build and test Minestom
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ master ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up JDK 11
|
||||||
|
uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: 1.11
|
||||||
|
- name: Grant execute permission for gradlew
|
||||||
|
run: chmod +x gradlew
|
||||||
|
- name: Build Minestom
|
||||||
|
run: ./gradlew build
|
||||||
|
- name: Run tests
|
||||||
|
run: ./gradlew test
|
30
build.gradle
30
build.gradle
@ -8,6 +8,10 @@ plugins {
|
|||||||
id 'org.jetbrains.kotlin.jvm' version '1.4.10'
|
id 'org.jetbrains.kotlin.jvm' version '1.4.10'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
group 'net.minestom.server'
|
||||||
|
version '1.0'
|
||||||
|
|
||||||
|
sourceCompatibility = 1.11
|
||||||
project.ext.lwjglVersion = "3.2.3"
|
project.ext.lwjglVersion = "3.2.3"
|
||||||
|
|
||||||
switch (OperatingSystem.current()) {
|
switch (OperatingSystem.current()) {
|
||||||
@ -37,17 +41,12 @@ allprojects {
|
|||||||
}
|
}
|
||||||
javadoc {
|
javadoc {
|
||||||
options {
|
options {
|
||||||
|
destinationDir(file("docs"))
|
||||||
addBooleanOption('html5', true)
|
addBooleanOption('html5', true)
|
||||||
addBooleanOption('-no-module-directories', true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
group 'net.minestom.server'
|
|
||||||
version '1.0'
|
|
||||||
|
|
||||||
sourceCompatibility = 1.11
|
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main {
|
main {
|
||||||
java {
|
java {
|
||||||
@ -101,12 +100,13 @@ dependencies {
|
|||||||
testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.6.2')
|
testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.6.2')
|
||||||
|
|
||||||
// Netty
|
// Netty
|
||||||
api 'io.netty:netty-handler:4.1.52.Final'
|
api 'io.netty:netty-handler:4.1.54.Final'
|
||||||
api 'io.netty:netty-codec:4.1.52.Final'
|
api 'io.netty:netty-codec:4.1.54.Final'
|
||||||
implementation 'io.netty:netty-transport-native-epoll:4.1.52.Final:linux-x86_64'
|
api 'io.netty:netty-transport-native-epoll:4.1.54.Final:linux-x86_64'
|
||||||
|
api 'io.netty:netty-transport-native-kqueue:4.1.54.Final:osx-x86_64'
|
||||||
|
|
||||||
// https://mvnrepository.com/artifact/it.unimi.dsi/fastutil
|
// https://mvnrepository.com/artifact/it.unimi.dsi/fastutil
|
||||||
api 'it.unimi.dsi:fastutil:8.4.2'
|
api 'it.unimi.dsi:fastutil:8.4.3'
|
||||||
|
|
||||||
// https://mvnrepository.com/artifact/com.google.code.gson/gson
|
// https://mvnrepository.com/artifact/com.google.code.gson/gson
|
||||||
api 'com.google.code.gson:gson:2.8.6'
|
api 'com.google.code.gson:gson:2.8.6'
|
||||||
@ -116,25 +116,21 @@ dependencies {
|
|||||||
api 'com.github.Articdive:Jnoise:1.0.0'
|
api 'com.github.Articdive:Jnoise:1.0.0'
|
||||||
|
|
||||||
// https://mvnrepository.com/artifact/org.rocksdb/rocksdbjni
|
// https://mvnrepository.com/artifact/org.rocksdb/rocksdbjni
|
||||||
api 'org.rocksdb:rocksdbjni:6.11.4'
|
api 'org.rocksdb:rocksdbjni:6.13.3'
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
api 'org.apache.logging.log4j:log4j-core:2.13.3'
|
api 'org.apache.logging.log4j:log4j-core:2.14.0'
|
||||||
// SLF4J is the base logger for most libraries, therefore we can hook it into log4j2.
|
// SLF4J is the base logger for most libraries, therefore we can hook it into log4j2.
|
||||||
api 'org.apache.logging.log4j:log4j-slf4j-impl:2.13.3'
|
api 'org.apache.logging.log4j:log4j-slf4j-impl:2.14.0'
|
||||||
|
|
||||||
api 'com.mojang:authlib:1.5.21'
|
api 'com.mojang:authlib:1.5.21'
|
||||||
|
|
||||||
api 'org.projectlombok:lombok:1.18.12'
|
|
||||||
annotationProcessor 'org.projectlombok:lombok:1.18.12'
|
|
||||||
|
|
||||||
// Code modification
|
// Code modification
|
||||||
api "org.ow2.asm:asm:${asmVersion}"
|
api "org.ow2.asm:asm:${asmVersion}"
|
||||||
api "org.ow2.asm:asm-tree:${asmVersion}"
|
api "org.ow2.asm:asm-tree:${asmVersion}"
|
||||||
api "org.ow2.asm:asm-analysis:${asmVersion}"
|
api "org.ow2.asm:asm-analysis:${asmVersion}"
|
||||||
api "org.ow2.asm:asm-util:${asmVersion}"
|
api "org.ow2.asm:asm-util:${asmVersion}"
|
||||||
api "org.ow2.asm:asm-commons:${asmVersion}"
|
api "org.ow2.asm:asm-commons:${asmVersion}"
|
||||||
implementation 'com.google.guava:guava:21.0'
|
|
||||||
api "org.spongepowered:mixin:${mixinVersion}"
|
api "org.spongepowered:mixin:${mixinVersion}"
|
||||||
|
|
||||||
// Path finding
|
// Path finding
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
asmVersion=8.0.1
|
asmVersion=9.0
|
||||||
mixinVersion=0.8
|
mixinVersion=0.8.1
|
||||||
hephaistos_version=v1.1.5
|
hephaistos_version=v1.1.5
|
@ -1,23 +1,21 @@
|
|||||||
package net.minestom.codegen;
|
package net.minestom.codegen;
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
public class PrismarinePaths {
|
public class PrismarinePaths {
|
||||||
|
|
||||||
@Getter private String blocks;
|
private String blocks;
|
||||||
@Getter private String biomes;
|
private String biomes;
|
||||||
@Getter private String effects;
|
private String effects;
|
||||||
@Getter private String items;
|
private String items;
|
||||||
@Getter private String recipes;
|
private String recipes;
|
||||||
@Getter private String instruments;
|
private String instruments;
|
||||||
@Getter private String materials;
|
private String materials;
|
||||||
@Getter private String entities;
|
private String entities;
|
||||||
@Getter private String protocol;
|
private String protocol;
|
||||||
@Getter private String windows;
|
private String windows;
|
||||||
@Getter private String version;
|
private String version;
|
||||||
@Getter private String language;
|
private String language;
|
||||||
|
|
||||||
public File getBlockFile() {
|
public File getBlockFile() {
|
||||||
return getFile(blocks, "blocks");
|
return getFile(blocks, "blocks");
|
||||||
@ -32,6 +30,6 @@ public class PrismarinePaths {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public File getFile(String path, String type) {
|
public File getFile(String path, String type) {
|
||||||
return new File("prismarine-minecraft-data/data/"+path+"/"+type+".json");
|
return new File("prismarine-minecraft-data/data/" + path + "/" + type + ".json");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,5 @@
|
|||||||
package net.minestom.server;
|
package net.minestom.server;
|
||||||
|
|
||||||
import com.mojang.authlib.AuthenticationService;
|
|
||||||
import com.mojang.authlib.minecraft.MinecraftSessionService;
|
|
||||||
import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
import net.minestom.server.advancements.AdvancementManager;
|
import net.minestom.server.advancements.AdvancementManager;
|
||||||
import net.minestom.server.benchmark.BenchmarkManager;
|
import net.minestom.server.benchmark.BenchmarkManager;
|
||||||
import net.minestom.server.command.CommandManager;
|
import net.minestom.server.command.CommandManager;
|
||||||
@ -16,7 +11,6 @@ import net.minestom.server.entity.EntityType;
|
|||||||
import net.minestom.server.entity.Player;
|
import net.minestom.server.entity.Player;
|
||||||
import net.minestom.server.extensions.Extension;
|
import net.minestom.server.extensions.Extension;
|
||||||
import net.minestom.server.extensions.ExtensionManager;
|
import net.minestom.server.extensions.ExtensionManager;
|
||||||
import net.minestom.server.extras.mojangAuth.MojangCrypt;
|
|
||||||
import net.minestom.server.fluids.Fluid;
|
import net.minestom.server.fluids.Fluid;
|
||||||
import net.minestom.server.gamedata.loottables.LootTableManager;
|
import net.minestom.server.gamedata.loottables.LootTableManager;
|
||||||
import net.minestom.server.gamedata.tags.TagManager;
|
import net.minestom.server.gamedata.tags.TagManager;
|
||||||
@ -31,7 +25,6 @@ import net.minestom.server.item.Material;
|
|||||||
import net.minestom.server.listener.manager.PacketListenerManager;
|
import net.minestom.server.listener.manager.PacketListenerManager;
|
||||||
import net.minestom.server.network.ConnectionManager;
|
import net.minestom.server.network.ConnectionManager;
|
||||||
import net.minestom.server.network.PacketProcessor;
|
import net.minestom.server.network.PacketProcessor;
|
||||||
import net.minestom.server.network.PacketWriterUtils;
|
|
||||||
import net.minestom.server.network.netty.NettyServer;
|
import net.minestom.server.network.netty.NettyServer;
|
||||||
import net.minestom.server.network.packet.server.play.PluginMessagePacket;
|
import net.minestom.server.network.packet.server.play.PluginMessagePacket;
|
||||||
import net.minestom.server.network.packet.server.play.ServerDifficultyPacket;
|
import net.minestom.server.network.packet.server.play.ServerDifficultyPacket;
|
||||||
@ -49,6 +42,7 @@ import net.minestom.server.storage.StorageLocation;
|
|||||||
import net.minestom.server.storage.StorageManager;
|
import net.minestom.server.storage.StorageManager;
|
||||||
import net.minestom.server.timer.SchedulerManager;
|
import net.minestom.server.timer.SchedulerManager;
|
||||||
import net.minestom.server.utils.MathUtils;
|
import net.minestom.server.utils.MathUtils;
|
||||||
|
import net.minestom.server.utils.PacketUtils;
|
||||||
import net.minestom.server.utils.thread.MinestomThread;
|
import net.minestom.server.utils.thread.MinestomThread;
|
||||||
import net.minestom.server.utils.validate.Check;
|
import net.minestom.server.utils.validate.Check;
|
||||||
import net.minestom.server.world.Difficulty;
|
import net.minestom.server.world.Difficulty;
|
||||||
@ -60,8 +54,6 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.Proxy;
|
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -72,8 +64,7 @@ import java.util.Collection;
|
|||||||
*/
|
*/
|
||||||
public final class MinecraftServer {
|
public final class MinecraftServer {
|
||||||
|
|
||||||
@Getter
|
public final static Logger LOGGER = LoggerFactory.getLogger(MinecraftServer.class);
|
||||||
private final static Logger LOGGER = LoggerFactory.getLogger(MinecraftServer.class);
|
|
||||||
|
|
||||||
public static final String VERSION_NAME = "1.16.4";
|
public static final String VERSION_NAME = "1.16.4";
|
||||||
public static final int PROTOCOL_VERSION = 754;
|
public static final int PROTOCOL_VERSION = 754;
|
||||||
@ -85,9 +76,6 @@ public final class MinecraftServer {
|
|||||||
|
|
||||||
public static final String THREAD_NAME_TICK = "Ms-Tick";
|
public static final String THREAD_NAME_TICK = "Ms-Tick";
|
||||||
|
|
||||||
public static final String THREAD_NAME_PACKET_WRITER = "Ms-PacketWriterPool";
|
|
||||||
public static final int THREAD_COUNT_PACKET_WRITER = 2;
|
|
||||||
|
|
||||||
public static final String THREAD_NAME_BLOCK_BATCH = "Ms-BlockBatchPool";
|
public static final String THREAD_NAME_BLOCK_BATCH = "Ms-BlockBatchPool";
|
||||||
public static final int THREAD_COUNT_BLOCK_BATCH = 2;
|
public static final int THREAD_COUNT_BLOCK_BATCH = 2;
|
||||||
|
|
||||||
@ -103,21 +91,12 @@ public final class MinecraftServer {
|
|||||||
private static final int MS_TO_SEC = 1000;
|
private static final int MS_TO_SEC = 1000;
|
||||||
public static final int TICK_MS = MS_TO_SEC / TICK_PER_SECOND;
|
public static final int TICK_MS = MS_TO_SEC / TICK_PER_SECOND;
|
||||||
|
|
||||||
@Getter
|
// Network monitoring
|
||||||
@Setter
|
private static int rateLimit = 300;
|
||||||
private static boolean hardcoreLook = false;
|
private static int maxPacketSize = 30_000;
|
||||||
|
// Network
|
||||||
//Extras
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private static boolean fixLighting = true;
|
|
||||||
|
|
||||||
//Rate Limiting
|
|
||||||
private static int rateLimit = 0;
|
|
||||||
// TODO
|
|
||||||
public static final int MAX_PACKET_SIZE = 300_000;
|
|
||||||
|
|
||||||
private static PacketListenerManager packetListenerManager;
|
private static PacketListenerManager packetListenerManager;
|
||||||
|
private static PacketProcessor packetProcessor;
|
||||||
private static NettyServer nettyServer;
|
private static NettyServer nettyServer;
|
||||||
|
|
||||||
// In-Game Manager
|
// In-Game Manager
|
||||||
@ -154,14 +133,6 @@ public final class MinecraftServer {
|
|||||||
private static LootTableManager lootTableManager;
|
private static LootTableManager lootTableManager;
|
||||||
private static TagManager tagManager;
|
private static TagManager tagManager;
|
||||||
|
|
||||||
//Mojang Auth
|
|
||||||
@Getter
|
|
||||||
private static final KeyPair keyPair = MojangCrypt.generateKeyPair();
|
|
||||||
@Getter
|
|
||||||
private static final AuthenticationService authService = new YggdrasilAuthenticationService(Proxy.NO_PROXY, "");
|
|
||||||
@Getter
|
|
||||||
private static final MinecraftSessionService sessionService = authService.createMinecraftSessionService();
|
|
||||||
|
|
||||||
public static MinecraftServer init() {
|
public static MinecraftServer init() {
|
||||||
if (minecraftServer != null) // don't init twice
|
if (minecraftServer != null) // don't init twice
|
||||||
return minecraftServer;
|
return minecraftServer;
|
||||||
@ -185,7 +156,7 @@ public final class MinecraftServer {
|
|||||||
|
|
||||||
connectionManager = new ConnectionManager();
|
connectionManager = new ConnectionManager();
|
||||||
// Networking
|
// Networking
|
||||||
final PacketProcessor packetProcessor = new PacketProcessor();
|
packetProcessor = new PacketProcessor();
|
||||||
packetListenerManager = new PacketListenerManager();
|
packetListenerManager = new PacketListenerManager();
|
||||||
|
|
||||||
instanceManager = new InstanceManager();
|
instanceManager = new InstanceManager();
|
||||||
@ -239,19 +210,17 @@ public final class MinecraftServer {
|
|||||||
* @param brandName the server brand name
|
* @param brandName the server brand name
|
||||||
* @throws NullPointerException if {@code brandName} is null
|
* @throws NullPointerException if {@code brandName} is null
|
||||||
*/
|
*/
|
||||||
@NotNull
|
public static void setBrandName(@NotNull String brandName) {
|
||||||
public static void setBrandName(String brandName) {
|
|
||||||
Check.notNull(brandName, "The brand name cannot be null");
|
Check.notNull(brandName, "The brand name cannot be null");
|
||||||
MinecraftServer.brandName = brandName;
|
MinecraftServer.brandName = brandName;
|
||||||
|
|
||||||
PluginMessagePacket brandMessage = PluginMessagePacket.getBrandPacket();
|
PacketUtils.sendGroupedPacket(connectionManager.getOnlinePlayers(), PluginMessagePacket.getBrandPacket());
|
||||||
PacketWriterUtils.writeAndSend(connectionManager.getOnlinePlayers(), brandMessage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the max number of packets a client can send over 1 second.
|
* Gets the maximum number of packets a client can send over 1 second.
|
||||||
*
|
*
|
||||||
* @return the packet count limit over 1 second
|
* @return the packet count limit over 1 second, 0 if not enabled
|
||||||
*/
|
*/
|
||||||
public static int getRateLimit() {
|
public static int getRateLimit() {
|
||||||
return rateLimit;
|
return rateLimit;
|
||||||
@ -266,6 +235,24 @@ public final class MinecraftServer {
|
|||||||
MinecraftServer.rateLimit = rateLimit;
|
MinecraftServer.rateLimit = rateLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the maximum packet size (in bytes) that a client can send without getting disconnected.
|
||||||
|
*
|
||||||
|
* @return the maximum packet size
|
||||||
|
*/
|
||||||
|
public static int getMaxPacketSize() {
|
||||||
|
return maxPacketSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the maximum packet size (in bytes) that a client can send without getting disconnected.
|
||||||
|
*
|
||||||
|
* @param maxPacketSize the new max packet size
|
||||||
|
*/
|
||||||
|
public static void setMaxPacketSize(int maxPacketSize) {
|
||||||
|
MinecraftServer.maxPacketSize = maxPacketSize;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the server difficulty showed in game option.
|
* Gets the server difficulty showed in game option.
|
||||||
*
|
*
|
||||||
@ -281,17 +268,15 @@ public final class MinecraftServer {
|
|||||||
*
|
*
|
||||||
* @param difficulty the new server difficulty
|
* @param difficulty the new server difficulty
|
||||||
*/
|
*/
|
||||||
@NotNull
|
|
||||||
public static void setDifficulty(@NotNull Difficulty difficulty) {
|
public static void setDifficulty(@NotNull Difficulty difficulty) {
|
||||||
Check.notNull(difficulty, "The server difficulty cannot be null.");
|
Check.notNull(difficulty, "The server difficulty cannot be null.");
|
||||||
MinecraftServer.difficulty = difficulty;
|
MinecraftServer.difficulty = difficulty;
|
||||||
|
|
||||||
// The difficulty packet
|
// Send the packet to all online players
|
||||||
ServerDifficultyPacket serverDifficultyPacket = new ServerDifficultyPacket();
|
ServerDifficultyPacket serverDifficultyPacket = new ServerDifficultyPacket();
|
||||||
serverDifficultyPacket.difficulty = difficulty;
|
serverDifficultyPacket.difficulty = difficulty;
|
||||||
serverDifficultyPacket.locked = true; // Can only be modified on single-player
|
serverDifficultyPacket.locked = true; // Can only be modified on single-player
|
||||||
// Send the packet to all online players
|
PacketUtils.sendGroupedPacket(connectionManager.getOnlinePlayers(), serverDifficultyPacket);
|
||||||
PacketWriterUtils.writeAndSend(connectionManager.getOnlinePlayers(), serverDifficultyPacket);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -424,6 +409,18 @@ public final class MinecraftServer {
|
|||||||
return connectionManager;
|
return connectionManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the object handling the client packets processing.
|
||||||
|
* <p>
|
||||||
|
* Can be used if you want to convert a buffer to a client packet object.
|
||||||
|
*
|
||||||
|
* @return the packet processor
|
||||||
|
*/
|
||||||
|
public static PacketProcessor getPacketProcessor() {
|
||||||
|
checkInitStatus(packetProcessor);
|
||||||
|
return packetProcessor;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets if the server is up and running.
|
* Gets if the server is up and running.
|
||||||
*
|
*
|
||||||
@ -453,14 +450,16 @@ public final class MinecraftServer {
|
|||||||
"The chunk view distance must be between 2 and 32");
|
"The chunk view distance must be between 2 and 32");
|
||||||
MinecraftServer.chunkViewDistance = chunkViewDistance;
|
MinecraftServer.chunkViewDistance = chunkViewDistance;
|
||||||
if (started) {
|
if (started) {
|
||||||
UpdateViewDistancePacket updateViewDistancePacket = new UpdateViewDistancePacket();
|
|
||||||
updateViewDistancePacket.viewDistance = chunkViewDistance;
|
|
||||||
|
|
||||||
final Collection<Player> players = connectionManager.getOnlinePlayers();
|
final Collection<Player> players = connectionManager.getOnlinePlayers();
|
||||||
|
|
||||||
PacketWriterUtils.writeAndSend(players, updateViewDistancePacket);
|
UpdateViewDistancePacket updateViewDistancePacket = new UpdateViewDistancePacket();
|
||||||
|
updateViewDistancePacket.viewDistance = chunkViewDistance;
|
||||||
|
|
||||||
connectionManager.getOnlinePlayers().forEach(player -> {
|
// Send packet to all online players
|
||||||
|
PacketUtils.sendGroupedPacket(players, updateViewDistancePacket);
|
||||||
|
|
||||||
|
players.forEach(player -> {
|
||||||
final Chunk playerChunk = player.getChunk();
|
final Chunk playerChunk = player.getChunk();
|
||||||
if (playerChunk != null) {
|
if (playerChunk != null) {
|
||||||
player.refreshVisibleChunks(playerChunk);
|
player.refreshVisibleChunks(playerChunk);
|
||||||
@ -625,11 +624,11 @@ public final class MinecraftServer {
|
|||||||
extensionManager.getExtensions().forEach(Extension::initialize);
|
extensionManager.getExtensions().forEach(Extension::initialize);
|
||||||
extensionManager.getExtensions().forEach(Extension::postInitialize);
|
extensionManager.getExtensions().forEach(Extension::postInitialize);
|
||||||
|
|
||||||
|
MinecraftServer.started = true;
|
||||||
|
|
||||||
final double loadTime = MathUtils.round((t1 + System.nanoTime()) / 1_000_000D, 2);
|
final double loadTime = MathUtils.round((t1 + System.nanoTime()) / 1_000_000D, 2);
|
||||||
LOGGER.info("Extensions loaded in " + loadTime + "ms");
|
LOGGER.info("Extensions loaded in " + loadTime + "ms");
|
||||||
LOGGER.info("Minestom server started successfully.");
|
LOGGER.info("Minestom server started successfully.");
|
||||||
|
|
||||||
MinecraftServer.started = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,23 +1,19 @@
|
|||||||
package net.minestom.server;
|
package net.minestom.server;
|
||||||
|
|
||||||
import net.minestom.server.chat.ChatColor;
|
|
||||||
import net.minestom.server.chat.ColoredText;
|
|
||||||
import net.minestom.server.entity.EntityManager;
|
import net.minestom.server.entity.EntityManager;
|
||||||
import net.minestom.server.entity.Player;
|
|
||||||
import net.minestom.server.instance.Instance;
|
import net.minestom.server.instance.Instance;
|
||||||
import net.minestom.server.instance.InstanceManager;
|
import net.minestom.server.instance.InstanceManager;
|
||||||
import net.minestom.server.network.ConnectionManager;
|
|
||||||
import net.minestom.server.network.packet.server.play.KeepAlivePacket;
|
|
||||||
import net.minestom.server.thread.PerGroupChunkProvider;
|
import net.minestom.server.thread.PerGroupChunkProvider;
|
||||||
import net.minestom.server.thread.ThreadProvider;
|
import net.minestom.server.thread.ThreadProvider;
|
||||||
import net.minestom.server.utils.thread.MinestomThread;
|
import net.minestom.server.utils.thread.MinestomThread;
|
||||||
import net.minestom.server.utils.validate.Check;
|
import net.minestom.server.utils.validate.Check;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.function.DoubleConsumer;
|
import java.util.function.LongConsumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manager responsible for the server ticks.
|
* Manager responsible for the server ticks.
|
||||||
@ -27,37 +23,30 @@ import java.util.function.DoubleConsumer;
|
|||||||
*/
|
*/
|
||||||
public final class UpdateManager {
|
public final class UpdateManager {
|
||||||
|
|
||||||
private static final long KEEP_ALIVE_DELAY = 10_000;
|
|
||||||
private static final long KEEP_ALIVE_KICK = 30_000;
|
|
||||||
private static final ColoredText TIMEOUT_TEXT = ColoredText.of(ChatColor.RED + "Timeout");
|
|
||||||
|
|
||||||
private final ExecutorService mainUpdate = new MinestomThread(1, MinecraftServer.THREAD_NAME_MAIN_UPDATE);
|
private final ExecutorService mainUpdate = new MinestomThread(1, MinecraftServer.THREAD_NAME_MAIN_UPDATE);
|
||||||
private boolean stopRequested;
|
private boolean stopRequested;
|
||||||
|
|
||||||
private ThreadProvider threadProvider;
|
private ThreadProvider threadProvider;
|
||||||
|
|
||||||
private final ConcurrentLinkedQueue<Runnable> tickStartCallbacks = new ConcurrentLinkedQueue<>();
|
private final ConcurrentLinkedQueue<LongConsumer> tickStartCallbacks = new ConcurrentLinkedQueue<>();
|
||||||
private final ConcurrentLinkedQueue<DoubleConsumer> tickEndCallbacks = new ConcurrentLinkedQueue<>();
|
private final ConcurrentLinkedQueue<LongConsumer> tickEndCallbacks = new ConcurrentLinkedQueue<>();
|
||||||
|
|
||||||
{
|
{
|
||||||
// DEFAULT THREAD PROVIDER
|
// DEFAULT THREAD PROVIDER
|
||||||
//threadProvider = new PerInstanceThreadProvider();
|
|
||||||
threadProvider = new PerGroupChunkProvider();
|
threadProvider = new PerGroupChunkProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should only be created in MinecraftServer
|
* Should only be created in MinecraftServer.
|
||||||
*/
|
*/
|
||||||
protected UpdateManager() {
|
protected UpdateManager() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts the server loop in the update thread
|
* Starts the server loop in the update thread.
|
||||||
*/
|
*/
|
||||||
protected void start() {
|
protected void start() {
|
||||||
mainUpdate.execute(() -> {
|
mainUpdate.execute(() -> {
|
||||||
|
|
||||||
final ConnectionManager connectionManager = MinecraftServer.getConnectionManager();
|
|
||||||
final EntityManager entityManager = MinecraftServer.getEntityManager();
|
final EntityManager entityManager = MinecraftServer.getEntityManager();
|
||||||
|
|
||||||
final long tickDistance = MinecraftServer.TICK_MS * 1000000;
|
final long tickDistance = MinecraftServer.TICK_MS * 1000000;
|
||||||
@ -67,56 +56,25 @@ public final class UpdateManager {
|
|||||||
final long tickStart = System.currentTimeMillis();
|
final long tickStart = System.currentTimeMillis();
|
||||||
|
|
||||||
// Tick start callbacks
|
// Tick start callbacks
|
||||||
if (!tickStartCallbacks.isEmpty()) {
|
doTickCallback(tickStartCallbacks, tickStart);
|
||||||
Runnable callback;
|
|
||||||
while ((callback = tickStartCallbacks.poll()) != null) {
|
|
||||||
callback.run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Future<?>> futures;
|
|
||||||
|
|
||||||
// Server tick (instance/chunk/entity)
|
|
||||||
// Synchronize with the update manager instance, like the signal for chunk load/unload
|
|
||||||
synchronized (this) {
|
|
||||||
futures = threadProvider.update(tickStart);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Waiting players update (newly connected clients waiting to get into the server)
|
// Waiting players update (newly connected clients waiting to get into the server)
|
||||||
entityManager.updateWaitingPlayers();
|
entityManager.updateWaitingPlayers();
|
||||||
|
|
||||||
// Keep Alive Handling
|
// Keep Alive Handling
|
||||||
final KeepAlivePacket keepAlivePacket = new KeepAlivePacket(tickStart);
|
entityManager.handleKeepAlive(tickStart);
|
||||||
for (Player player : connectionManager.getOnlinePlayers()) {
|
|
||||||
final long lastKeepAlive = tickStart - player.getLastKeepAlive();
|
|
||||||
if (lastKeepAlive > KEEP_ALIVE_DELAY && player.didAnswerKeepAlive()) {
|
|
||||||
player.refreshKeepAlive(tickStart);
|
|
||||||
player.getPlayerConnection().sendPacket(keepAlivePacket);
|
|
||||||
} else if (lastKeepAlive >= KEEP_ALIVE_KICK) {
|
|
||||||
player.kick(TIMEOUT_TEXT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final Future<?> future : futures) {
|
// Server tick (chunks/entities)
|
||||||
try {
|
serverTick(tickStart);
|
||||||
future.get();
|
|
||||||
} catch (Throwable e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// the time that the tick took in nanoseconds
|
||||||
|
final long tickTime = System.nanoTime() - currentTime;
|
||||||
|
|
||||||
// Tick end callbacks
|
// Tick end callbacks
|
||||||
if (!tickEndCallbacks.isEmpty()) {
|
doTickCallback(tickEndCallbacks, tickTime / 1000000L);
|
||||||
final double tickEnd = (System.nanoTime() - currentTime) / 1000000D;
|
|
||||||
DoubleConsumer callback;
|
|
||||||
while ((callback = tickEndCallbacks.poll()) != null) {
|
|
||||||
callback.accept(tickEnd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sleep until next tick
|
// Sleep until next tick
|
||||||
final long sleepTime = Math.max(1, (tickDistance - (System.nanoTime() - currentTime)) / 1000000);
|
final long sleepTime = Math.max(1, (tickDistance - tickTime) / 1000000L);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Thread.sleep(sleepTime);
|
Thread.sleep(sleepTime);
|
||||||
@ -128,6 +86,44 @@ public final class UpdateManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a server tick and returns only once all the futures are completed.
|
||||||
|
*
|
||||||
|
* @param tickStart the time of the tick in milliseconds
|
||||||
|
*/
|
||||||
|
private void serverTick(long tickStart) {
|
||||||
|
List<Future<?>> futures;
|
||||||
|
|
||||||
|
// Server tick (instance/chunk/entity)
|
||||||
|
// Synchronize with the update manager instance, like the signal for chunk load/unload
|
||||||
|
synchronized (this) {
|
||||||
|
futures = threadProvider.update(tickStart);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final Future<?> future : futures) {
|
||||||
|
try {
|
||||||
|
future.get();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to execute tick-related callbacks.
|
||||||
|
*
|
||||||
|
* @param callbacks the callbacks to execute
|
||||||
|
* @param value the value to give to the consumers
|
||||||
|
*/
|
||||||
|
private void doTickCallback(ConcurrentLinkedQueue<LongConsumer> callbacks, long value) {
|
||||||
|
if (!callbacks.isEmpty()) {
|
||||||
|
LongConsumer callback;
|
||||||
|
while ((callback = callbacks.poll()) != null) {
|
||||||
|
callback.accept(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the current {@link ThreadProvider}.
|
* Gets the current {@link ThreadProvider}.
|
||||||
*
|
*
|
||||||
@ -206,10 +202,12 @@ public final class UpdateManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a callback executed at the start of the next server tick.
|
* Adds a callback executed at the start of the next server tick.
|
||||||
|
* <p>
|
||||||
|
* The long in the consumer represents the starting time (in ms) of the tick.
|
||||||
*
|
*
|
||||||
* @param callback the tick start callback
|
* @param callback the tick start callback
|
||||||
*/
|
*/
|
||||||
public void addTickStartCallback(Runnable callback) {
|
public void addTickStartCallback(@NotNull LongConsumer callback) {
|
||||||
this.tickStartCallbacks.add(callback);
|
this.tickStartCallbacks.add(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,18 +216,18 @@ public final class UpdateManager {
|
|||||||
*
|
*
|
||||||
* @param callback the callback to remove
|
* @param callback the callback to remove
|
||||||
*/
|
*/
|
||||||
public void removeTickStartCallback(Runnable callback) {
|
public void removeTickStartCallback(@NotNull LongConsumer callback) {
|
||||||
this.tickStartCallbacks.remove(callback);
|
this.tickStartCallbacks.remove(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a callback executed at the end of the next server tick.
|
* Adds a callback executed at the end of the next server tick.
|
||||||
* <p>
|
* <p>
|
||||||
* The double in the consumer represents the duration (in ms) of the tick.
|
* The long in the consumer represents the duration (in ms) of the tick.
|
||||||
*
|
*
|
||||||
* @param callback the tick end callback
|
* @param callback the tick end callback
|
||||||
*/
|
*/
|
||||||
public void addTickEndCallback(DoubleConsumer callback) {
|
public void addTickEndCallback(@NotNull LongConsumer callback) {
|
||||||
this.tickEndCallbacks.add(callback);
|
this.tickEndCallbacks.add(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,12 +236,12 @@ public final class UpdateManager {
|
|||||||
*
|
*
|
||||||
* @param callback the callback to remove
|
* @param callback the callback to remove
|
||||||
*/
|
*/
|
||||||
public void removeTickEndCallback(DoubleConsumer callback) {
|
public void removeTickEndCallback(@NotNull LongConsumer callback) {
|
||||||
this.tickEndCallbacks.remove(callback);
|
this.tickEndCallbacks.remove(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops the server loop
|
* Stops the server loop.
|
||||||
*/
|
*/
|
||||||
public void stop() {
|
public void stop() {
|
||||||
stopRequested = true;
|
stopRequested = true;
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
package net.minestom.server;
|
package net.minestom.server;
|
||||||
|
|
||||||
import net.minestom.server.entity.Player;
|
import net.minestom.server.entity.Player;
|
||||||
import net.minestom.server.network.PacketWriterUtils;
|
|
||||||
import net.minestom.server.network.packet.server.ServerPacket;
|
import net.minestom.server.network.packet.server.ServerPacket;
|
||||||
|
import net.minestom.server.utils.PacketUtils;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -56,7 +55,7 @@ public interface Viewable {
|
|||||||
* @param packet the packet to send to all viewers
|
* @param packet the packet to send to all viewers
|
||||||
*/
|
*/
|
||||||
default void sendPacketToViewers(@NotNull ServerPacket packet) {
|
default void sendPacketToViewers(@NotNull ServerPacket packet) {
|
||||||
PacketWriterUtils.writeAndSend(getViewers(), packet);
|
PacketUtils.sendGroupedPacket(getViewers(), packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -69,40 +68,22 @@ public interface Viewable {
|
|||||||
*/
|
*/
|
||||||
default void sendPacketsToViewers(@NotNull ServerPacket... packets) {
|
default void sendPacketsToViewers(@NotNull ServerPacket... packets) {
|
||||||
for (ServerPacket packet : packets) {
|
for (ServerPacket packet : packets) {
|
||||||
PacketWriterUtils.writeAndSend(getViewers(), packet);
|
PacketUtils.sendGroupedPacket(getViewers(), packet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a packet to all viewers and the viewable element if it is a player.
|
* Sends a packet to all viewers and the viewable element if it is a player.
|
||||||
* <p>
|
* <p>
|
||||||
* If 'this' isn't a player, then {@link #sendPacketToViewers(ServerPacket)} is called instead.
|
* If 'this' isn't a player, then {only @link #sendPacketToViewers(ServerPacket)} is called.
|
||||||
*
|
*
|
||||||
* @param packet the packet to send
|
* @param packet the packet to send
|
||||||
*/
|
*/
|
||||||
default void sendPacketToViewersAndSelf(@NotNull ServerPacket packet) {
|
default void sendPacketToViewersAndSelf(@NotNull ServerPacket packet) {
|
||||||
if (this instanceof Player) {
|
if (this instanceof Player) {
|
||||||
if (getViewers().isEmpty()) {
|
((Player) this).getPlayerConnection().sendPacket(packet);
|
||||||
PacketWriterUtils.writeAndSend((Player) this, packet);
|
|
||||||
} else {
|
|
||||||
UNSAFE_sendPacketToViewersAndSelf(packet);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sendPacketToViewers(packet);
|
|
||||||
}
|
}
|
||||||
}
|
sendPacketToViewers(packet);
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a packet to all the viewers and 'this'.
|
|
||||||
* <p>
|
|
||||||
* Unsafe because of a cast to {@link Player} without any check beforehand.
|
|
||||||
*
|
|
||||||
* @param packet the packet to send
|
|
||||||
*/
|
|
||||||
private void UNSAFE_sendPacketToViewersAndSelf(@NotNull ServerPacket packet) {
|
|
||||||
Set<Player> recipients = new HashSet<>(getViewers());
|
|
||||||
recipients.add((Player) this);
|
|
||||||
PacketWriterUtils.writeAndSend(recipients, packet);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
package net.minestom.server.advancements;
|
package net.minestom.server.advancements;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
import net.minestom.server.chat.ColoredText;
|
import net.minestom.server.chat.ColoredText;
|
||||||
|
import net.minestom.server.entity.Player;
|
||||||
import net.minestom.server.item.ItemStack;
|
import net.minestom.server.item.ItemStack;
|
||||||
import net.minestom.server.item.Material;
|
import net.minestom.server.item.Material;
|
||||||
import net.minestom.server.network.packet.server.play.AdvancementsPacket;
|
import net.minestom.server.network.packet.server.play.AdvancementsPacket;
|
||||||
import net.minestom.server.network.player.PlayerConnection;
|
import net.minestom.server.utils.PacketUtils;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an advancement located in an {@link AdvancementTab}.
|
* Represents an advancement located in an {@link AdvancementTab}.
|
||||||
@ -373,17 +374,11 @@ public class Advancement {
|
|||||||
updateCriteria();
|
updateCriteria();
|
||||||
|
|
||||||
if (tab != null) {
|
if (tab != null) {
|
||||||
// Update the tab cached packet
|
final Set<Player> viewers = tab.getViewers();
|
||||||
tab.updatePacket();
|
AdvancementsPacket createPacket = tab.createPacket();
|
||||||
|
|
||||||
final ByteBuf createBuffer = tab.createBuffer;
|
PacketUtils.sendGroupedPacket(viewers, tab.removePacket);
|
||||||
final ByteBuf removeBuffer = tab.removeBuffer;
|
PacketUtils.sendGroupedPacket(viewers, createPacket);
|
||||||
tab.getViewers().forEach(player -> {
|
|
||||||
final PlayerConnection playerConnection = player.getPlayerConnection();
|
|
||||||
// Receive order is important
|
|
||||||
playerConnection.sendPacket(removeBuffer, true);
|
|
||||||
playerConnection.sendPacket(createBuffer, true);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
package net.minestom.server.advancements;
|
package net.minestom.server.advancements;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
import net.minestom.server.Viewable;
|
import net.minestom.server.Viewable;
|
||||||
import net.minestom.server.entity.Player;
|
import net.minestom.server.entity.Player;
|
||||||
import net.minestom.server.network.packet.server.play.AdvancementsPacket;
|
import net.minestom.server.network.packet.server.play.AdvancementsPacket;
|
||||||
import net.minestom.server.network.player.PlayerConnection;
|
import net.minestom.server.network.player.PlayerConnection;
|
||||||
import net.minestom.server.utils.PacketUtils;
|
|
||||||
import net.minestom.server.utils.advancement.AdvancementUtils;
|
import net.minestom.server.utils.advancement.AdvancementUtils;
|
||||||
import net.minestom.server.utils.validate.Check;
|
import net.minestom.server.utils.validate.Check;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@ -14,7 +12,7 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a tab which can be shared between multiple players.
|
* Represents a tab which can be shared between multiple players. Created using {@link AdvancementManager#createTab(String, AdvancementRoot)}.
|
||||||
* <p>
|
* <p>
|
||||||
* Each tab requires a root advancement and all succeeding advancements need to have a parent in the tab.
|
* Each tab requires a root advancement and all succeeding advancements need to have a parent in the tab.
|
||||||
* You can create a new advancement using {@link #createAdvancement(String, Advancement, Advancement)}.
|
* You can create a new advancement using {@link #createAdvancement(String, Advancement, Advancement)}.
|
||||||
@ -33,19 +31,16 @@ public class AdvancementTab implements Viewable {
|
|||||||
// Advancement -> its parent
|
// Advancement -> its parent
|
||||||
private final Map<Advancement, Advancement> advancementMap = new HashMap<>();
|
private final Map<Advancement, Advancement> advancementMap = new HashMap<>();
|
||||||
|
|
||||||
// Packet cache, updated every time the tab changes
|
|
||||||
protected ByteBuf createBuffer;
|
|
||||||
// the packet used to clear the tab (used to remove it and to update an advancement)
|
// the packet used to clear the tab (used to remove it and to update an advancement)
|
||||||
// will never change (since the root identifier is always the same)
|
// will never change (since the root identifier is always the same)
|
||||||
protected final ByteBuf removeBuffer;
|
protected final AdvancementsPacket removePacket;
|
||||||
|
|
||||||
protected AdvancementTab(@NotNull String rootIdentifier, @NotNull AdvancementRoot root) {
|
protected AdvancementTab(@NotNull String rootIdentifier, @NotNull AdvancementRoot root) {
|
||||||
this.root = root;
|
this.root = root;
|
||||||
|
|
||||||
cacheAdvancement(rootIdentifier, root, null);
|
cacheAdvancement(rootIdentifier, root, null);
|
||||||
|
|
||||||
final AdvancementsPacket removePacket = AdvancementUtils.getRemovePacket(new String[]{rootIdentifier});
|
this.removePacket = AdvancementUtils.getRemovePacket(new String[]{rootIdentifier});
|
||||||
this.removeBuffer = PacketUtils.writePacket(removePacket);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -88,13 +83,6 @@ public class AdvancementTab implements Viewable {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the packet buffer.
|
|
||||||
*/
|
|
||||||
protected void updatePacket() {
|
|
||||||
this.createBuffer = PacketUtils.writePacket(createPacket());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the packet which build the whole advancement tab.
|
* Builds the packet which build the whole advancement tab.
|
||||||
*
|
*
|
||||||
@ -135,8 +123,6 @@ public class AdvancementTab implements Viewable {
|
|||||||
advancement.setParent(parent);
|
advancement.setParent(parent);
|
||||||
advancement.updateCriteria();
|
advancement.updateCriteria();
|
||||||
this.advancementMap.put(advancement, parent);
|
this.advancementMap.put(advancement, parent);
|
||||||
|
|
||||||
updatePacket();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -149,7 +135,7 @@ public class AdvancementTab implements Viewable {
|
|||||||
final PlayerConnection playerConnection = player.getPlayerConnection();
|
final PlayerConnection playerConnection = player.getPlayerConnection();
|
||||||
|
|
||||||
// Send the tab to the player
|
// Send the tab to the player
|
||||||
playerConnection.sendPacket(createBuffer, true);
|
playerConnection.sendPacket(createPacket());
|
||||||
|
|
||||||
addPlayer(player);
|
addPlayer(player);
|
||||||
|
|
||||||
@ -166,7 +152,7 @@ public class AdvancementTab implements Viewable {
|
|||||||
|
|
||||||
// Remove the tab
|
// Remove the tab
|
||||||
if (!player.isRemoved()) {
|
if (!player.isRemoved()) {
|
||||||
playerConnection.sendPacket(removeBuffer, true);
|
playerConnection.sendPacket(removePacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
removePlayer(player);
|
removePlayer(player);
|
||||||
|
@ -38,7 +38,6 @@ public final class BenchmarkManager {
|
|||||||
THREAD_MX_BEAN.setThreadCpuTimeEnabled(true);
|
THREAD_MX_BEAN.setThreadCpuTimeEnabled(true);
|
||||||
|
|
||||||
THREADS.add(THREAD_NAME_MAIN_UPDATE);
|
THREADS.add(THREAD_NAME_MAIN_UPDATE);
|
||||||
THREADS.add(THREAD_NAME_PACKET_WRITER);
|
|
||||||
THREADS.add(THREAD_NAME_BLOCK_BATCH);
|
THREADS.add(THREAD_NAME_BLOCK_BATCH);
|
||||||
THREADS.add(THREAD_NAME_SCHEDULER);
|
THREADS.add(THREAD_NAME_SCHEDULER);
|
||||||
THREADS.add(THREAD_NAME_TICK);
|
THREADS.add(THREAD_NAME_TICK);
|
||||||
@ -92,7 +91,7 @@ public final class BenchmarkManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the memory used by the server in bytes.
|
* Gets the heap memory used by the server in bytes.
|
||||||
*
|
*
|
||||||
* @return the memory used by the server
|
* @return the memory used by the server
|
||||||
*/
|
*/
|
||||||
|
@ -15,7 +15,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||||||
/**
|
/**
|
||||||
* Represents a boss bar which is displayed on the top of the client screen (max amount of boss bar defined by {@link #MAX_BOSSBAR}).
|
* Represents a boss bar which is displayed on the top of the client screen (max amount of boss bar defined by {@link #MAX_BOSSBAR}).
|
||||||
* <p>
|
* <p>
|
||||||
* To use it, create a new instance and add the {@link Player} you want using {@link #addViewer(Player)} and remove them using {@link #removeViewer(Player)}.
|
* To use it, create a new instance using the constructor
|
||||||
|
* and add the {@link Player} you want using {@link #addViewer(Player)} and remove them using {@link #removeViewer(Player)}.
|
||||||
* <p>
|
* <p>
|
||||||
* You can retrieve all the boss bars of a {@link Player} with {@link #getBossBars(Player)}.
|
* You can retrieve all the boss bars of a {@link Player} with {@link #getBossBars(Player)}.
|
||||||
*/
|
*/
|
||||||
|
@ -9,8 +9,6 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
// TODO format retention
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents multiple {@link ColoredText} batched together with the possibility to add
|
* Represents multiple {@link ColoredText} batched together with the possibility to add
|
||||||
* click and hover events.
|
* click and hover events.
|
||||||
@ -42,18 +40,30 @@ public class RichMessage extends JsonMessage {
|
|||||||
Check.notNull(coloredText, "ColoredText cannot be null");
|
Check.notNull(coloredText, "ColoredText cannot be null");
|
||||||
|
|
||||||
RichMessage richMessage = new RichMessage();
|
RichMessage richMessage = new RichMessage();
|
||||||
appendText(richMessage, coloredText, FormatRetention.ALL);
|
appendText(richMessage, coloredText);
|
||||||
|
|
||||||
return richMessage;
|
return richMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void appendText(@NotNull RichMessage richMessage, @NotNull ColoredText coloredText,
|
private static void appendText(@NotNull RichMessage richMessage, @NotNull ColoredText coloredText) {
|
||||||
@NotNull FormatRetention formatRetention) {
|
RichComponent component = new RichComponent(coloredText);
|
||||||
RichComponent component = new RichComponent(coloredText, formatRetention);
|
|
||||||
richMessage.components.add(component);
|
richMessage.components.add(component);
|
||||||
richMessage.currentComponent = component;
|
richMessage.currentComponent = component;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new rich component to the message.
|
||||||
|
*
|
||||||
|
* @param coloredText the text composing the rich component
|
||||||
|
* @return the rich message
|
||||||
|
*/
|
||||||
|
public RichMessage append(@NotNull ColoredText coloredText) {
|
||||||
|
Check.notNull(coloredText, "ColoredText cannot be null");
|
||||||
|
|
||||||
|
appendText(this, coloredText);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the click event of the current rich component.
|
* Sets the click event of the current rich component.
|
||||||
*
|
*
|
||||||
@ -87,31 +97,6 @@ public class RichMessage extends JsonMessage {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a new rich component to the message.
|
|
||||||
*
|
|
||||||
* @param coloredText the text composing the rich component
|
|
||||||
* @param formatRetention the format retention of the added component
|
|
||||||
* @return the rich message
|
|
||||||
*/
|
|
||||||
public RichMessage append(@NotNull ColoredText coloredText, @NotNull FormatRetention formatRetention) {
|
|
||||||
Check.notNull(coloredText, "ColoredText cannot be null");
|
|
||||||
|
|
||||||
appendText(this, coloredText, formatRetention);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a new rich component to the message,
|
|
||||||
* the format retention is set to {@link FormatRetention#ALL}.
|
|
||||||
*
|
|
||||||
* @param coloredText the text composing the rich component
|
|
||||||
* @return the rich message
|
|
||||||
*/
|
|
||||||
public RichMessage append(@NotNull ColoredText coloredText) {
|
|
||||||
return append(coloredText, FormatRetention.ALL);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public JsonObject getJsonObject() {
|
public JsonObject getJsonObject() {
|
||||||
@ -121,18 +106,14 @@ public class RichMessage extends JsonMessage {
|
|||||||
if (cacheComponents.isEmpty())
|
if (cacheComponents.isEmpty())
|
||||||
return new JsonObject();
|
return new JsonObject();
|
||||||
|
|
||||||
RichComponent firstComponent = cacheComponents.remove(0);
|
// The main object contains the extra array, with an empty text to do not share its state with the others
|
||||||
List<JsonObject> firstComponentObjects = getComponentObject(firstComponent);
|
JsonObject mainObject = new JsonObject();
|
||||||
JsonObject mainObject = firstComponentObjects.remove(0);
|
mainObject.addProperty("text", "");
|
||||||
|
|
||||||
if (cacheComponents.isEmpty() && firstComponentObjects.isEmpty())
|
|
||||||
return mainObject;
|
|
||||||
|
|
||||||
|
// The extra array contains all the components
|
||||||
JsonArray extraArray = new JsonArray();
|
JsonArray extraArray = new JsonArray();
|
||||||
for (JsonObject firstComponentObject : firstComponentObjects) {
|
|
||||||
extraArray.add(firstComponentObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Add all the components
|
||||||
for (RichComponent component : cacheComponents) {
|
for (RichComponent component : cacheComponents) {
|
||||||
List<JsonObject> componentObjects = getComponentObject(component);
|
List<JsonObject> componentObjects = getComponentObject(component);
|
||||||
for (JsonObject componentObject : componentObjects) {
|
for (JsonObject componentObject : componentObjects) {
|
||||||
@ -205,24 +186,18 @@ public class RichMessage extends JsonMessage {
|
|||||||
return eventObject;
|
return eventObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum FormatRetention {
|
|
||||||
ALL, CLICK_EVENT, HOVER_EVENT, NONE
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a {@link ColoredText} with a click and hover event (can be null).
|
* Represents a {@link ColoredText} with a click and hover event (can be null).
|
||||||
*/
|
*/
|
||||||
private static class RichComponent {
|
private static class RichComponent {
|
||||||
|
|
||||||
private final ColoredText text;
|
private final ColoredText text;
|
||||||
private final FormatRetention formatRetention;
|
|
||||||
private ChatClickEvent clickEvent;
|
private ChatClickEvent clickEvent;
|
||||||
private ChatHoverEvent hoverEvent;
|
private ChatHoverEvent hoverEvent;
|
||||||
private String insertion;
|
private String insertion;
|
||||||
|
|
||||||
private RichComponent(@NotNull ColoredText text, @NotNull FormatRetention formatRetention) {
|
private RichComponent(@NotNull ColoredText text) {
|
||||||
this.text = text;
|
this.text = text;
|
||||||
this.formatRetention = formatRetention;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@ -230,11 +205,6 @@ public class RichMessage extends JsonMessage {
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public FormatRetention getFormatRetention() {
|
|
||||||
return formatRetention;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public ChatClickEvent getClickEvent() {
|
public ChatClickEvent getClickEvent() {
|
||||||
return clickEvent;
|
return clickEvent;
|
||||||
|
@ -83,8 +83,15 @@ public final class CommandManager {
|
|||||||
* Registers a {@link Command}.
|
* Registers a {@link Command}.
|
||||||
*
|
*
|
||||||
* @param command the command to register
|
* @param command the command to register
|
||||||
|
* @throws IllegalStateException if a command with the same name already exists
|
||||||
*/
|
*/
|
||||||
public void register(@NotNull Command command) {
|
public synchronized void register(@NotNull Command command) {
|
||||||
|
Check.stateCondition(commandExists(command.getName()),
|
||||||
|
"A command with the name " + command.getName() + " is already registered!");
|
||||||
|
for (String alias : command.getAliases()) {
|
||||||
|
Check.stateCondition(commandExists(alias),
|
||||||
|
"A command with the name " + alias + " is already registered!");
|
||||||
|
}
|
||||||
this.dispatcher.register(command);
|
this.dispatcher.register(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,13 +110,20 @@ public final class CommandManager {
|
|||||||
* Registers a {@link CommandProcessor}.
|
* Registers a {@link CommandProcessor}.
|
||||||
*
|
*
|
||||||
* @param commandProcessor the command to register
|
* @param commandProcessor the command to register
|
||||||
|
* @throws IllegalStateException if a command with the same name already exists
|
||||||
*/
|
*/
|
||||||
public void register(@NotNull CommandProcessor commandProcessor) {
|
public synchronized void register(@NotNull CommandProcessor commandProcessor) {
|
||||||
this.commandProcessorMap.put(commandProcessor.getCommandName().toLowerCase(), commandProcessor);
|
final String commandName = commandProcessor.getCommandName().toLowerCase();
|
||||||
|
Check.stateCondition(commandExists(commandName),
|
||||||
|
"A command with the name " + commandName + " is already registered!");
|
||||||
|
this.commandProcessorMap.put(commandName, commandProcessor);
|
||||||
// Register aliases
|
// Register aliases
|
||||||
final String[] aliases = commandProcessor.getAliases();
|
final String[] aliases = commandProcessor.getAliases();
|
||||||
if (aliases != null && aliases.length > 0) {
|
if (aliases != null && aliases.length > 0) {
|
||||||
for (String alias : aliases) {
|
for (String alias : aliases) {
|
||||||
|
Check.stateCondition(commandExists(alias),
|
||||||
|
"A command with the name " + alias + " is already registered!");
|
||||||
|
|
||||||
this.commandProcessorMap.put(alias.toLowerCase(), commandProcessor);
|
this.commandProcessorMap.put(alias.toLowerCase(), commandProcessor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -126,6 +140,18 @@ public final class CommandManager {
|
|||||||
return commandProcessorMap.get(commandName.toLowerCase());
|
return commandProcessorMap.get(commandName.toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets if a command with the name {@code commandName} already exists or name.
|
||||||
|
*
|
||||||
|
* @param commandName the command name to check
|
||||||
|
* @return true if the command does exist
|
||||||
|
*/
|
||||||
|
public boolean commandExists(@NotNull String commandName) {
|
||||||
|
commandName = commandName.toLowerCase();
|
||||||
|
return dispatcher.findCommand(commandName) != null ||
|
||||||
|
commandProcessorMap.get(commandName) != null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes a command for a {@link ConsoleSender}.
|
* Executes a command for a {@link ConsoleSender}.
|
||||||
*
|
*
|
||||||
|
@ -12,6 +12,8 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
* <p>
|
* <p>
|
||||||
* Tab-completion can be activated by overriding {@link #enableWritingTracking()} and return true, you should then listen to
|
* Tab-completion can be activated by overriding {@link #enableWritingTracking()} and return true, you should then listen to
|
||||||
* {@link #onWrite(String)} and return the possible completions to suggest.
|
* {@link #onWrite(String)} and return the possible completions to suggest.
|
||||||
|
* <p>
|
||||||
|
* Please be sure to check {@link net.minestom.server.command.builder.Command} as it is likely to be better for your use case.
|
||||||
*/
|
*/
|
||||||
public interface CommandProcessor {
|
public interface CommandProcessor {
|
||||||
|
|
||||||
|
@ -1,18 +1,15 @@
|
|||||||
package net.minestom.server.command;
|
package net.minestom.server.command;
|
||||||
|
|
||||||
import net.minestom.server.entity.Player;
|
import net.minestom.server.entity.Player;
|
||||||
import net.minestom.server.permission.Permission;
|
import net.minestom.server.permission.PermissionHandler;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents something which can send commands to the server.
|
* Represents something which can send commands to the server.
|
||||||
* <p>
|
* <p>
|
||||||
* Main implementations are {@link Player} and {@link ConsoleSender}.
|
* Main implementations are {@link Player} and {@link ConsoleSender}.
|
||||||
*/
|
*/
|
||||||
public interface CommandSender {
|
public interface CommandSender extends PermissionHandler {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a raw string message.
|
* Sends a raw string message.
|
||||||
@ -32,85 +29,6 @@ public interface CommandSender {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all permissions associated to this command sender.
|
|
||||||
* The returned collection should be modified only by subclasses.
|
|
||||||
*
|
|
||||||
* @return the permissions of this command sender.
|
|
||||||
*/
|
|
||||||
@NotNull
|
|
||||||
Collection<Permission> getAllPermissions();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a {@link Permission} to this commandSender
|
|
||||||
*
|
|
||||||
* @param permission the permission to add
|
|
||||||
*/
|
|
||||||
default void addPermission(@NotNull Permission permission) {
|
|
||||||
getAllPermissions().add(permission);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a {@link Permission} from this commandSender
|
|
||||||
*
|
|
||||||
* @param permission the permission to remove
|
|
||||||
*/
|
|
||||||
default void removePermission(@NotNull Permission permission) {
|
|
||||||
getAllPermissions().remove(permission);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the given {@link Permission} is possessed by this command sender.
|
|
||||||
* Simple shortcut to <pre>getAllPermissions().contains(permission) && permission.isValidFor(this)</pre> for readability.
|
|
||||||
*
|
|
||||||
* @param p permission to check against
|
|
||||||
* @return true if the sender has the permission and validate {@link Permission#isValidFor(CommandSender, Object)}
|
|
||||||
*/
|
|
||||||
default boolean hasPermission(@NotNull Permission p) {
|
|
||||||
return hasPermission(p, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
default <T> boolean hasPermission(@NotNull Permission<T> p, @Nullable T data) {
|
|
||||||
return getAllPermissions().contains(p) && p.isValidFor(this, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the given {@link Permission} is possessed by this command sender.
|
|
||||||
* Will call {@link Permission#isValidFor(CommandSender, Object)} on all permissions that are an instance of {@code permissionClass}.
|
|
||||||
* If no matching permission is found, this result returns false.
|
|
||||||
*
|
|
||||||
* @param permissionClass the permission class to check
|
|
||||||
* @return true if the sender has the permission and validate {@link Permission#isValidFor(CommandSender, Object)}
|
|
||||||
* @see #getAllPermissions()
|
|
||||||
*/
|
|
||||||
default boolean hasPermission(@NotNull Class<? extends Permission> permissionClass) {
|
|
||||||
boolean result = true;
|
|
||||||
boolean foundPerm = false;
|
|
||||||
for (Permission p : getAllPermissions()) {
|
|
||||||
if (permissionClass.isInstance(p)) {
|
|
||||||
foundPerm = true;
|
|
||||||
result &= p.isValidFor(this, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!foundPerm)
|
|
||||||
return false;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
default <T> boolean hasPermission(@NotNull Class<? extends Permission<T>> permissionClass, @Nullable T data) {
|
|
||||||
boolean result = true;
|
|
||||||
boolean foundPerm = false;
|
|
||||||
for (Permission p : getAllPermissions()) {
|
|
||||||
if (permissionClass.isInstance(p)) {
|
|
||||||
foundPerm = true;
|
|
||||||
result &= p.isValidFor(this, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!foundPerm)
|
|
||||||
return false;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets if the sender is a {@link Player}.
|
* Gets if the sender is a {@link Player}.
|
||||||
*
|
*
|
||||||
@ -133,6 +51,7 @@ public interface CommandSender {
|
|||||||
* Casts this object to a {@link Player}.
|
* Casts this object to a {@link Player}.
|
||||||
* No checks are performed, {@link ClassCastException} can very much happen.
|
* No checks are performed, {@link ClassCastException} can very much happen.
|
||||||
*
|
*
|
||||||
|
* @throws ClassCastException if 'this' is not a player
|
||||||
* @see #isPlayer()
|
* @see #isPlayer()
|
||||||
*/
|
*/
|
||||||
default Player asPlayer() {
|
default Player asPlayer() {
|
||||||
@ -143,6 +62,7 @@ public interface CommandSender {
|
|||||||
* Casts this object to a {@link ConsoleSender}.
|
* Casts this object to a {@link ConsoleSender}.
|
||||||
* No checks are performed, {@link ClassCastException} can very much happen.
|
* No checks are performed, {@link ClassCastException} can very much happen.
|
||||||
*
|
*
|
||||||
|
* @throws ClassCastException if 'this' is not a console sender
|
||||||
* @see #isConsole()
|
* @see #isConsole()
|
||||||
*/
|
*/
|
||||||
default ConsoleSender asConsole() {
|
default ConsoleSender asConsole() {
|
||||||
|
@ -58,7 +58,7 @@ public class Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link Command} with a name without any aliases.
|
* Creates a {@link Command} with a name and no alias.
|
||||||
*
|
*
|
||||||
* @param name the name of the command
|
* @param name the name of the command
|
||||||
* @see #Command(String, String...)
|
* @see #Command(String, String...)
|
||||||
@ -70,8 +70,10 @@ public class Command {
|
|||||||
/**
|
/**
|
||||||
* Gets the {@link CommandCondition}.
|
* Gets the {@link CommandCondition}.
|
||||||
* <p>
|
* <p>
|
||||||
* It is called no matter the syntax used and can be used to check permissions or
|
* It is called after the parsing and just before the execution no matter the syntax used and can be used to check permissions or
|
||||||
* the {@link CommandSender} type.
|
* the {@link CommandSender} type.
|
||||||
|
* <p>
|
||||||
|
* Worth mentioning that the condition is also used to know if the command known from a player (at connection).
|
||||||
*
|
*
|
||||||
* @return the command condition, null if not any
|
* @return the command condition, null if not any
|
||||||
*/
|
*/
|
||||||
@ -84,6 +86,7 @@ public class Command {
|
|||||||
* Sets the {@link CommandCondition}.
|
* Sets the {@link CommandCondition}.
|
||||||
*
|
*
|
||||||
* @param commandCondition the new command condition, null to do not call anything
|
* @param commandCondition the new command condition, null to do not call anything
|
||||||
|
* @see #getCondition()
|
||||||
*/
|
*/
|
||||||
public void setCondition(@Nullable CommandCondition commandCondition) {
|
public void setCondition(@Nullable CommandCondition commandCondition) {
|
||||||
this.condition = commandCondition;
|
this.condition = commandCondition;
|
||||||
@ -104,7 +107,7 @@ public class Command {
|
|||||||
/**
|
/**
|
||||||
* Adds a new syntax in the command.
|
* Adds a new syntax in the command.
|
||||||
* <p>
|
* <p>
|
||||||
* A syntax is simply a list of arguments.
|
* A syntax is simply a list of arguments and an executor called when successfully parsed.
|
||||||
*
|
*
|
||||||
* @param commandCondition the condition to use the syntax
|
* @param commandCondition the condition to use the syntax
|
||||||
* @param executor the executor to call when the syntax is successfully received
|
* @param executor the executor to call when the syntax is successfully received
|
||||||
|
@ -17,6 +17,12 @@ public class CommandDispatcher {
|
|||||||
private final Map<String, Command> commandMap = new HashMap<>();
|
private final Map<String, Command> commandMap = new HashMap<>();
|
||||||
private final Set<Command> commands = new HashSet<>();
|
private final Set<Command> commands = new HashSet<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a command,
|
||||||
|
* be aware that registering a command name or alias will override the previous entry.
|
||||||
|
*
|
||||||
|
* @param command the command to register
|
||||||
|
*/
|
||||||
public void register(@NotNull Command command) {
|
public void register(@NotNull Command command) {
|
||||||
this.commandMap.put(command.getName().toLowerCase(), command);
|
this.commandMap.put(command.getName().toLowerCase(), command);
|
||||||
|
|
||||||
|
@ -12,11 +12,11 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
public interface CommandExecutor {
|
public interface CommandExecutor {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the command callback once the syntax has been called (or the default executor)
|
* Executes the command callback once the syntax has been called (or the default executor).
|
||||||
*
|
*
|
||||||
* @param source the sender of the command
|
* @param sender the sender of the command
|
||||||
* @param args contains all the parsed arguments,
|
* @param args contains all the parsed arguments,
|
||||||
* the id is the one initialized when creating the argument object
|
* the id is the one initialized when creating the argument object
|
||||||
*/
|
*/
|
||||||
void apply(@NotNull CommandSender source, @NotNull Arguments args);
|
void apply(@NotNull CommandSender sender, @NotNull Arguments args);
|
||||||
}
|
}
|
@ -3,6 +3,11 @@ package net.minestom.server.command.builder.arguments.relative;
|
|||||||
import net.minestom.server.command.builder.arguments.Argument;
|
import net.minestom.server.command.builder.arguments.Argument;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common interface for all the relative location arguments.
|
||||||
|
*
|
||||||
|
* @param <T> the relative location type
|
||||||
|
*/
|
||||||
public abstract class ArgumentRelative<T> extends Argument<T> {
|
public abstract class ArgumentRelative<T> extends Argument<T> {
|
||||||
|
|
||||||
public static final String RELATIVE_CHAR = "~";
|
public static final String RELATIVE_CHAR = "~";
|
||||||
|
@ -26,13 +26,23 @@ public class ArgumentRelativeBlockPosition extends ArgumentRelative<RelativeBloc
|
|||||||
|
|
||||||
// Check if each element is correct
|
// Check if each element is correct
|
||||||
for (String element : split) {
|
for (String element : split) {
|
||||||
if (!element.equals(RELATIVE_CHAR)) {
|
if (!element.startsWith(RELATIVE_CHAR)) {
|
||||||
try {
|
try {
|
||||||
// Will throw the exception if not an integer
|
// Will throw the exception if not an integer
|
||||||
Integer.parseInt(element);
|
Integer.parseInt(element);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
return INVALID_NUMBER_ERROR;
|
return INVALID_NUMBER_ERROR;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (element.length() > RELATIVE_CHAR.length()) {
|
||||||
|
try {
|
||||||
|
final String potentialNumber = element.substring(1);
|
||||||
|
// Will throw the exception if not an integer
|
||||||
|
Integer.parseInt(potentialNumber);
|
||||||
|
} catch (NumberFormatException | IndexOutOfBoundsException e) {
|
||||||
|
return INVALID_NUMBER_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,7 +61,8 @@ public class ArgumentRelativeBlockPosition extends ArgumentRelative<RelativeBloc
|
|||||||
|
|
||||||
for (int i = 0; i < split.length; i++) {
|
for (int i = 0; i < split.length; i++) {
|
||||||
final String element = split[i];
|
final String element = split[i];
|
||||||
if (element.equals(RELATIVE_CHAR)) {
|
if (element.startsWith(RELATIVE_CHAR)) {
|
||||||
|
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
relativeX = true;
|
relativeX = true;
|
||||||
} else if (i == 1) {
|
} else if (i == 1) {
|
||||||
@ -59,6 +70,19 @@ public class ArgumentRelativeBlockPosition extends ArgumentRelative<RelativeBloc
|
|||||||
} else if (i == 2) {
|
} else if (i == 2) {
|
||||||
relativeZ = true;
|
relativeZ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (element.length() != RELATIVE_CHAR.length()) {
|
||||||
|
final String potentialNumber = element.substring(1);
|
||||||
|
final int number = Integer.parseInt(potentialNumber);
|
||||||
|
if (i == 0) {
|
||||||
|
blockPosition.setX(number);
|
||||||
|
} else if (i == 1) {
|
||||||
|
blockPosition.setY(number);
|
||||||
|
} else if (i == 2) {
|
||||||
|
blockPosition.setZ(number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
final int number = Integer.parseInt(element);
|
final int number = Integer.parseInt(element);
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
|
@ -0,0 +1,54 @@
|
|||||||
|
package net.minestom.server.command.builder.arguments.relative;
|
||||||
|
|
||||||
|
import net.minestom.server.utils.location.RelativeVec;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common super class for {@link ArgumentRelativeVec2} and {@link ArgumentRelativeVec3}.
|
||||||
|
*/
|
||||||
|
public abstract class ArgumentRelativeVec extends ArgumentRelative<RelativeVec> {
|
||||||
|
|
||||||
|
public ArgumentRelativeVec(@NotNull String id, int numberCount) {
|
||||||
|
super(id, numberCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCorrectionResult(@NotNull String value) {
|
||||||
|
final String[] split = value.split(" ");
|
||||||
|
|
||||||
|
// Check if the value has enough element to be correct
|
||||||
|
if (split.length != getNumberCount()) {
|
||||||
|
return INVALID_NUMBER_COUNT_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if each element is correct
|
||||||
|
for (String element : split) {
|
||||||
|
if (!element.startsWith(RELATIVE_CHAR)) {
|
||||||
|
try {
|
||||||
|
// Will throw the exception if not a float
|
||||||
|
Float.parseFloat(element);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return INVALID_NUMBER_ERROR;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (element.length() > RELATIVE_CHAR.length()) {
|
||||||
|
try {
|
||||||
|
final String potentialNumber = element.substring(1);
|
||||||
|
// Will throw the exception if not a float
|
||||||
|
Float.parseFloat(potentialNumber);
|
||||||
|
} catch (NumberFormatException | IndexOutOfBoundsException e) {
|
||||||
|
return INVALID_NUMBER_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getConditionResult(@NotNull RelativeVec value) {
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -9,36 +9,12 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
* <p>
|
* <p>
|
||||||
* Example: -1.2 ~
|
* Example: -1.2 ~
|
||||||
*/
|
*/
|
||||||
public class ArgumentRelativeVec2 extends ArgumentRelative<RelativeVec> {
|
public class ArgumentRelativeVec2 extends ArgumentRelativeVec {
|
||||||
|
|
||||||
public ArgumentRelativeVec2(@NotNull String id) {
|
public ArgumentRelativeVec2(@NotNull String id) {
|
||||||
super(id, 2);
|
super(id, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getCorrectionResult(@NotNull String value) {
|
|
||||||
final String[] split = value.split(" ");
|
|
||||||
|
|
||||||
// Check if the value has enough element to be correct
|
|
||||||
if (split.length != getNumberCount()) {
|
|
||||||
return INVALID_NUMBER_COUNT_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if each element is correct
|
|
||||||
for (String element : split) {
|
|
||||||
if (!element.equals(RELATIVE_CHAR)) {
|
|
||||||
try {
|
|
||||||
// Will throw the exception if not a float
|
|
||||||
Float.parseFloat(element);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
return INVALID_NUMBER_ERROR;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public RelativeVec parse(@NotNull String value) {
|
public RelativeVec parse(@NotNull String value) {
|
||||||
@ -50,12 +26,23 @@ public class ArgumentRelativeVec2 extends ArgumentRelative<RelativeVec> {
|
|||||||
|
|
||||||
for (int i = 0; i < split.length; i++) {
|
for (int i = 0; i < split.length; i++) {
|
||||||
final String element = split[i];
|
final String element = split[i];
|
||||||
if (element.equals(RELATIVE_CHAR)) {
|
if (element.startsWith(RELATIVE_CHAR)) {
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
relativeX = true;
|
relativeX = true;
|
||||||
} else if (i == 1) {
|
} else if (i == 1) {
|
||||||
relativeZ = true;
|
relativeZ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (element.length() != RELATIVE_CHAR.length()) {
|
||||||
|
final String potentialNumber = element.substring(1);
|
||||||
|
final float number = Float.parseFloat(potentialNumber);
|
||||||
|
if (i == 0) {
|
||||||
|
vector.setX(number);
|
||||||
|
} else if (i == 1) {
|
||||||
|
vector.setZ(number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
final float number = Float.parseFloat(element);
|
final float number = Float.parseFloat(element);
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
@ -69,8 +56,4 @@ public class ArgumentRelativeVec2 extends ArgumentRelative<RelativeVec> {
|
|||||||
return new RelativeVec(vector, relativeX, false, relativeZ);
|
return new RelativeVec(vector, relativeX, false, relativeZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getConditionResult(@NotNull RelativeVec value) {
|
|
||||||
return SUCCESS;
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -9,36 +9,12 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
* <p>
|
* <p>
|
||||||
* Example: -1.2 ~ 5
|
* Example: -1.2 ~ 5
|
||||||
*/
|
*/
|
||||||
public class ArgumentRelativeVec3 extends ArgumentRelative<RelativeVec> {
|
public class ArgumentRelativeVec3 extends ArgumentRelativeVec {
|
||||||
|
|
||||||
public ArgumentRelativeVec3(@NotNull String id) {
|
public ArgumentRelativeVec3(@NotNull String id) {
|
||||||
super(id, 3);
|
super(id, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getCorrectionResult(@NotNull String value) {
|
|
||||||
final String[] split = value.split(" ");
|
|
||||||
|
|
||||||
// Check if the value has enough element to be correct
|
|
||||||
if (split.length != getNumberCount()) {
|
|
||||||
return INVALID_NUMBER_COUNT_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if each element is correct
|
|
||||||
for (String element : split) {
|
|
||||||
if (!element.equals(RELATIVE_CHAR)) {
|
|
||||||
try {
|
|
||||||
// Will throw the exception if not a float
|
|
||||||
Float.parseFloat(element);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
return INVALID_NUMBER_ERROR;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public RelativeVec parse(@NotNull String value) {
|
public RelativeVec parse(@NotNull String value) {
|
||||||
@ -51,7 +27,8 @@ public class ArgumentRelativeVec3 extends ArgumentRelative<RelativeVec> {
|
|||||||
|
|
||||||
for (int i = 0; i < split.length; i++) {
|
for (int i = 0; i < split.length; i++) {
|
||||||
final String element = split[i];
|
final String element = split[i];
|
||||||
if (element.equals(RELATIVE_CHAR)) {
|
if (element.startsWith(RELATIVE_CHAR)) {
|
||||||
|
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
relativeX = true;
|
relativeX = true;
|
||||||
} else if (i == 1) {
|
} else if (i == 1) {
|
||||||
@ -59,6 +36,19 @@ public class ArgumentRelativeVec3 extends ArgumentRelative<RelativeVec> {
|
|||||||
} else if (i == 2) {
|
} else if (i == 2) {
|
||||||
relativeZ = true;
|
relativeZ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (element.length() != RELATIVE_CHAR.length()) {
|
||||||
|
final String potentialNumber = element.substring(1);
|
||||||
|
final float number = Float.parseFloat(potentialNumber);
|
||||||
|
if (i == 0) {
|
||||||
|
vector.setX(number);
|
||||||
|
} else if (i == 1) {
|
||||||
|
vector.setY(number);
|
||||||
|
} else if (i == 2) {
|
||||||
|
vector.setZ(number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
final float number = Float.parseFloat(element);
|
final float number = Float.parseFloat(element);
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
@ -73,9 +63,4 @@ public class ArgumentRelativeVec3 extends ArgumentRelative<RelativeVec> {
|
|||||||
|
|
||||||
return new RelativeVec(vector, relativeX, relativeY, relativeZ);
|
return new RelativeVec(vector, relativeX, relativeY, relativeZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getConditionResult(@NotNull RelativeVec value) {
|
|
||||||
return SUCCESS;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
@ -66,4 +67,17 @@ public class DataImpl implements Data {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
DataImpl data1 = (DataImpl) o;
|
||||||
|
return Objects.equals(data, data1.data) &&
|
||||||
|
Objects.equals(dataType, data1.dataType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(data, dataType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
public class NbtDataImpl extends DataImpl {
|
public class NbtDataImpl extends DataImpl {
|
||||||
|
|
||||||
// Used to know if a nbt key is from a Data object, should NOT be changed
|
// Used to know if a nbt key is from a Data object, should NOT be changed and used in a key name
|
||||||
public static final String KEY_PREFIX = "nbtdata_";
|
public static final String KEY_PREFIX = "nbtdata_";
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@ -45,7 +45,8 @@ public class NbtDataImpl extends DataImpl {
|
|||||||
Check.notNull(nbt,
|
Check.notNull(nbt,
|
||||||
"The type '" + type + "' is not supported within NbtDataImpl, if you wish to use a custom type you can encode the value into a byte array using a DataType");
|
"The type '" + type + "' is not supported within NbtDataImpl, if you wish to use a custom type you can encode the value into a byte array using a DataType");
|
||||||
|
|
||||||
nbtCompound.set(KEY_PREFIX + key, nbt);
|
final String finalKey = KEY_PREFIX + key;
|
||||||
|
nbtCompound.set(finalKey, nbt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ public interface SerializableData extends Data {
|
|||||||
DataManager DATA_MANAGER = MinecraftServer.getDataManager();
|
DataManager DATA_MANAGER = MinecraftServer.getDataManager();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
<T> void set(@NotNull String key, @Nullable T value, @NotNull Class<T> type);
|
<T> void set(@NotNull String key, @Nullable T value, @Nullable Class<T> type);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serializes the data into an array of bytes.
|
* Serializes the data into an array of bytes.
|
||||||
@ -109,7 +109,7 @@ public interface SerializableData extends Data {
|
|||||||
{
|
{
|
||||||
final int dataIndexSize = binaryReader.readVarInt();
|
final int dataIndexSize = binaryReader.readVarInt();
|
||||||
for (int i = 0; i < dataIndexSize; i++) {
|
for (int i = 0; i < dataIndexSize; i++) {
|
||||||
final String className = binaryReader.readSizedString();
|
final String className = binaryReader.readSizedString(Integer.MAX_VALUE);
|
||||||
final short classIndex = binaryReader.readShort();
|
final short classIndex = binaryReader.readShort();
|
||||||
typeToIndexMap.put(className, classIndex);
|
typeToIndexMap.put(className, classIndex);
|
||||||
}
|
}
|
||||||
|
@ -92,6 +92,7 @@ public class SerializableDataImpl extends DataImpl implements SerializableData {
|
|||||||
|
|
||||||
// Write the data (no length)
|
// Write the data (no length)
|
||||||
final DataType dataType = DATA_MANAGER.getDataType(type);
|
final DataType dataType = DATA_MANAGER.getDataType(type);
|
||||||
|
Check.notNull(dataType, "Tried to encode a type not registered in DataManager: " + type);
|
||||||
dataType.encode(binaryWriter, value);
|
dataType.encode(binaryWriter, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,7 +150,7 @@ public class SerializableDataImpl extends DataImpl implements SerializableData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the key
|
// Get the key
|
||||||
final String name = reader.readSizedString();
|
final String name = reader.readSizedString(Integer.MAX_VALUE);
|
||||||
|
|
||||||
// Get the data
|
// Get the data
|
||||||
final Object value;
|
final Object value;
|
||||||
|
@ -27,8 +27,8 @@ public class InventoryData extends DataType<Inventory> {
|
|||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public Inventory decode(@NotNull BinaryReader reader) {
|
public Inventory decode(@NotNull BinaryReader reader) {
|
||||||
final String title = reader.readSizedString();
|
final String title = reader.readSizedString(Integer.MAX_VALUE);
|
||||||
final InventoryType inventoryType = InventoryType.valueOf(reader.readSizedString());
|
final InventoryType inventoryType = InventoryType.valueOf(reader.readSizedString(Integer.MAX_VALUE));
|
||||||
final int size = inventoryType.getAdditionalSlot();
|
final int size = inventoryType.getAdditionalSlot();
|
||||||
|
|
||||||
Inventory inventory = new Inventory(inventoryType, title);
|
Inventory inventory = new Inventory(inventoryType, title);
|
||||||
|
@ -15,6 +15,6 @@ public class StringData extends DataType<String> {
|
|||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public String decode(@NotNull BinaryReader reader) {
|
public String decode(@NotNull BinaryReader reader) {
|
||||||
return reader.readSizedString();
|
return reader.readSizedString(Integer.MAX_VALUE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,19 +9,12 @@ public class StringArrayData extends DataType<String[]> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void encode(@NotNull BinaryWriter writer, @NotNull String[] value) {
|
public void encode(@NotNull BinaryWriter writer, @NotNull String[] value) {
|
||||||
writer.writeVarInt(value.length);
|
writer.writeStringArray(value);
|
||||||
for (String val : value) {
|
|
||||||
writer.writeSizedString(val);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public String[] decode(@NotNull BinaryReader reader) {
|
public String[] decode(@NotNull BinaryReader reader) {
|
||||||
String[] array = new String[reader.readVarInt()];
|
return reader.readSizedStringArray(Integer.MAX_VALUE);
|
||||||
for (int i = 0; i < array.length; i++) {
|
|
||||||
array[i] = reader.readSizedString();
|
|
||||||
}
|
|
||||||
return array;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,8 @@ import net.minestom.server.instance.InstanceManager;
|
|||||||
import net.minestom.server.instance.WorldBorder;
|
import net.minestom.server.instance.WorldBorder;
|
||||||
import net.minestom.server.instance.block.CustomBlock;
|
import net.minestom.server.instance.block.CustomBlock;
|
||||||
import net.minestom.server.network.packet.server.play.*;
|
import net.minestom.server.network.packet.server.play.*;
|
||||||
|
import net.minestom.server.permission.Permission;
|
||||||
|
import net.minestom.server.permission.PermissionHandler;
|
||||||
import net.minestom.server.thread.ThreadProvider;
|
import net.minestom.server.thread.ThreadProvider;
|
||||||
import net.minestom.server.utils.BlockPosition;
|
import net.minestom.server.utils.BlockPosition;
|
||||||
import net.minestom.server.utils.Position;
|
import net.minestom.server.utils.Position;
|
||||||
@ -41,7 +43,12 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public abstract class Entity implements Viewable, EventHandler, DataContainer {
|
/**
|
||||||
|
* Could be a player, a monster, or an object.
|
||||||
|
* <p>
|
||||||
|
* To create your own entity you probably want to extends {@link ObjectEntity} or {@link EntityCreature} instead.
|
||||||
|
*/
|
||||||
|
public abstract class Entity implements Viewable, EventHandler, DataContainer, PermissionHandler {
|
||||||
|
|
||||||
private static final Map<Integer, Entity> entityById = new ConcurrentHashMap<>();
|
private static final Map<Integer, Entity> entityById = new ConcurrentHashMap<>();
|
||||||
private static final AtomicInteger lastEntityId = new AtomicInteger();
|
private static final AtomicInteger lastEntityId = new AtomicInteger();
|
||||||
@ -82,8 +89,9 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer {
|
|||||||
|
|
||||||
private boolean autoViewable;
|
private boolean autoViewable;
|
||||||
private final int id;
|
private final int id;
|
||||||
private Data data;
|
|
||||||
protected final Set<Player> viewers = new CopyOnWriteArraySet<>();
|
protected final Set<Player> viewers = new CopyOnWriteArraySet<>();
|
||||||
|
private Data data;
|
||||||
|
private final List<Permission> permissions = new LinkedList<>();
|
||||||
|
|
||||||
protected UUID uuid;
|
protected UUID uuid;
|
||||||
private boolean isActive; // False if entity has only been instanced without being added somewhere
|
private boolean isActive; // False if entity has only been instanced without being added somewhere
|
||||||
@ -139,6 +147,10 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer {
|
|||||||
setVelocityUpdatePeriod(5);
|
setVelocityUpdatePeriod(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Entity(@NotNull EntityType entityType) {
|
||||||
|
this(entityType, new Position());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Schedules a task to be run during the next entity tick.
|
* Schedules a task to be run during the next entity tick.
|
||||||
* It ensures that the task will be executed in the same thread as the entity (depending of the {@link ThreadProvider}).
|
* It ensures that the task will be executed in the same thread as the entity (depending of the {@link ThreadProvider}).
|
||||||
@ -149,12 +161,12 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer {
|
|||||||
this.nextTick.add(callback);
|
this.nextTick.add(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Entity(@NotNull EntityType entityType) {
|
|
||||||
this(entityType, new Position());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param id the entity unique id ({@link #getEntityId()})
|
* Gets an entity based on its id (from {@link #getEntityId()}).
|
||||||
|
* <p>
|
||||||
|
* Entity id are unique server-wide.
|
||||||
|
*
|
||||||
|
* @param id the entity unique id
|
||||||
* @return the entity having the specified id, null if not found
|
* @return the entity having the specified id, null if not found
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
@ -169,7 +181,7 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer {
|
|||||||
/**
|
/**
|
||||||
* Called each tick.
|
* Called each tick.
|
||||||
*
|
*
|
||||||
* @param time the time of update in milliseconds
|
* @param time time of the update in milliseconds
|
||||||
*/
|
*/
|
||||||
public abstract void update(long time);
|
public abstract void update(long time);
|
||||||
|
|
||||||
@ -341,6 +353,12 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer {
|
|||||||
this.data = data;
|
this.data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Collection<Permission> getAllPermissions() {
|
||||||
|
return permissions;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the entity, called every tick.
|
* Updates the entity, called every tick.
|
||||||
* <p>
|
* <p>
|
||||||
@ -365,8 +383,9 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
BlockPosition blockPosition = position.toBlockPosition();
|
final Chunk currentChunk = getChunk(); // current entity chunk
|
||||||
if (!ChunkUtils.isLoaded(instance, position.getX(), position.getZ()) || !ChunkUtils.isLoaded(instance, blockPosition.getX(), blockPosition.getZ())) {
|
|
||||||
|
if (!ChunkUtils.isLoaded(currentChunk)) {
|
||||||
// No update for entities in unloaded chunk
|
// No update for entities in unloaded chunk
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -454,8 +473,8 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer {
|
|||||||
|
|
||||||
float drag;
|
float drag;
|
||||||
if (onGround) {
|
if (onGround) {
|
||||||
final CustomBlock customBlock =
|
final BlockPosition blockPosition = position.toBlockPosition();
|
||||||
instance.getCustomBlock(blockPosition);
|
final CustomBlock customBlock = instance.getCustomBlock(blockPosition);
|
||||||
if (customBlock != null) {
|
if (customBlock != null) {
|
||||||
// Custom drag
|
// Custom drag
|
||||||
drag = customBlock.getDrag(instance, blockPosition);
|
drag = customBlock.getDrag(instance, blockPosition);
|
||||||
@ -484,6 +503,7 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handle block contacts
|
// handle block contacts
|
||||||
|
// TODO do not call every tick (it is pretty expensive)
|
||||||
final int minX = (int) Math.floor(boundingBox.getMinX());
|
final int minX = (int) Math.floor(boundingBox.getMinX());
|
||||||
final int maxX = (int) Math.ceil(boundingBox.getMaxX());
|
final int maxX = (int) Math.ceil(boundingBox.getMaxX());
|
||||||
final int minY = (int) Math.floor(boundingBox.getMinY());
|
final int minY = (int) Math.floor(boundingBox.getMinY());
|
||||||
@ -576,11 +596,10 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Each entity has an unique id which will change after a restart.
|
* Each entity has an unique id (server-wide) which will change after a restart.
|
||||||
* <p>
|
|
||||||
* All entities can be retrieved by calling {@link Entity#getEntity(int)}.
|
|
||||||
*
|
*
|
||||||
* @return the unique entity id
|
* @return the unique entity id
|
||||||
|
* @see Entity#getEntity(int) to retrive an entity based on its id
|
||||||
*/
|
*/
|
||||||
public int getEntityId() {
|
public int getEntityId() {
|
||||||
return id;
|
return id;
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
package net.minestom.server.entity;
|
package net.minestom.server.entity;
|
||||||
|
|
||||||
import net.minestom.server.MinecraftServer;
|
import net.minestom.server.MinecraftServer;
|
||||||
|
import net.minestom.server.chat.ChatColor;
|
||||||
|
import net.minestom.server.chat.ColoredText;
|
||||||
import net.minestom.server.event.player.PlayerLoginEvent;
|
import net.minestom.server.event.player.PlayerLoginEvent;
|
||||||
import net.minestom.server.event.player.PlayerPreLoginEvent;
|
import net.minestom.server.event.player.PlayerPreLoginEvent;
|
||||||
import net.minestom.server.instance.Instance;
|
import net.minestom.server.instance.Instance;
|
||||||
|
import net.minestom.server.network.ConnectionManager;
|
||||||
|
import net.minestom.server.network.packet.server.play.KeepAlivePacket;
|
||||||
import net.minestom.server.utils.validate.Check;
|
import net.minestom.server.utils.validate.Check;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
@ -12,10 +17,16 @@ import java.util.function.Consumer;
|
|||||||
|
|
||||||
public final class EntityManager {
|
public final class EntityManager {
|
||||||
|
|
||||||
|
private static final ConnectionManager CONNECTION_MANAGER = MinecraftServer.getConnectionManager();
|
||||||
|
|
||||||
|
private static final long KEEP_ALIVE_DELAY = 10_000;
|
||||||
|
private static final long KEEP_ALIVE_KICK = 30_000;
|
||||||
|
private static final ColoredText TIMEOUT_TEXT = ColoredText.of(ChatColor.RED + "Timeout");
|
||||||
|
|
||||||
private final ConcurrentLinkedQueue<Player> waitingPlayers = new ConcurrentLinkedQueue<>();
|
private final ConcurrentLinkedQueue<Player> waitingPlayers = new ConcurrentLinkedQueue<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connect waiting players
|
* Connects waiting players.
|
||||||
*/
|
*/
|
||||||
public void updateWaitingPlayers() {
|
public void updateWaitingPlayers() {
|
||||||
// Connect waiting players
|
// Connect waiting players
|
||||||
@ -23,7 +34,25 @@ public final class EntityManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add connected clients after the handshake (used to free the networking threads)
|
* Updates keep alive by checking the last keep alive packet and send a new one if needed.
|
||||||
|
*
|
||||||
|
* @param tickStart the time of the update in milliseconds, forwarded to the packet
|
||||||
|
*/
|
||||||
|
public void handleKeepAlive(long tickStart) {
|
||||||
|
final KeepAlivePacket keepAlivePacket = new KeepAlivePacket(tickStart);
|
||||||
|
for (Player player : CONNECTION_MANAGER.getOnlinePlayers()) {
|
||||||
|
final long lastKeepAlive = tickStart - player.getLastKeepAlive();
|
||||||
|
if (lastKeepAlive > KEEP_ALIVE_DELAY && player.didAnswerKeepAlive()) {
|
||||||
|
player.refreshKeepAlive(tickStart);
|
||||||
|
player.getPlayerConnection().sendPacket(keepAlivePacket);
|
||||||
|
} else if (lastKeepAlive >= KEEP_ALIVE_KICK) {
|
||||||
|
player.kick(TIMEOUT_TEXT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds connected clients after the handshake (used to free the networking threads).
|
||||||
*/
|
*/
|
||||||
private void waitingPlayersTick() {
|
private void waitingPlayersTick() {
|
||||||
Player waitingPlayer;
|
Player waitingPlayer;
|
||||||
@ -41,14 +70,14 @@ public final class EntityManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call the player initialization callbacks and the event {@link PlayerPreLoginEvent}.
|
* Calls the player initialization callbacks and the event {@link PlayerPreLoginEvent}.
|
||||||
* If the {@link Player} hasn't been kicked, add him to the waiting list.
|
* If the {@link Player} hasn't been kicked, add him to the waiting list.
|
||||||
* <p>
|
* <p>
|
||||||
* Can be considered as a pre-init thing.
|
* Can be considered as a pre-init thing.
|
||||||
*
|
*
|
||||||
* @param player the {@link Player} to add
|
* @param player the {@link Player} to add
|
||||||
*/
|
*/
|
||||||
public void addWaitingPlayer(Player player) {
|
public void addWaitingPlayer(@NotNull Player player) {
|
||||||
|
|
||||||
// Init player (register events)
|
// Init player (register events)
|
||||||
for (Consumer<Player> playerInitialization : MinecraftServer.getConnectionManager().getPlayerInitializations()) {
|
for (Consumer<Player> playerInitialization : MinecraftServer.getConnectionManager().getPlayerInitializations()) {
|
||||||
|
@ -19,7 +19,9 @@ import net.minestom.server.sound.Sound;
|
|||||||
import net.minestom.server.sound.SoundCategory;
|
import net.minestom.server.sound.SoundCategory;
|
||||||
import net.minestom.server.utils.Position;
|
import net.minestom.server.utils.Position;
|
||||||
import net.minestom.server.utils.binary.BinaryWriter;
|
import net.minestom.server.utils.binary.BinaryWriter;
|
||||||
|
import net.minestom.server.utils.time.CooldownUtils;
|
||||||
import net.minestom.server.utils.time.TimeUnit;
|
import net.minestom.server.utils.time.TimeUnit;
|
||||||
|
import net.minestom.server.utils.time.UpdateOption;
|
||||||
import net.minestom.server.utils.validate.Check;
|
import net.minestom.server.utils.validate.Check;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
@ -29,7 +31,11 @@ import java.util.function.Consumer;
|
|||||||
|
|
||||||
public abstract class LivingEntity extends Entity implements EquipmentHandler {
|
public abstract class LivingEntity extends Entity implements EquipmentHandler {
|
||||||
|
|
||||||
|
// Item pickup
|
||||||
protected boolean canPickupItem;
|
protected boolean canPickupItem;
|
||||||
|
protected UpdateOption itemPickupCooldown = new UpdateOption(5, TimeUnit.TICK);
|
||||||
|
private long lastItemPickupCheckTime;
|
||||||
|
|
||||||
protected boolean isDead;
|
protected boolean isDead;
|
||||||
|
|
||||||
private float health;
|
private float health;
|
||||||
@ -90,8 +96,10 @@ public abstract class LivingEntity extends Entity implements EquipmentHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Items picking
|
// Items picking
|
||||||
if (canPickupItem()) {
|
if (canPickupItem() && !CooldownUtils.hasCooldown(time, lastItemPickupCheckTime, itemPickupCooldown)) {
|
||||||
final Chunk chunk = instance.getChunkAt(getPosition()); // TODO check surrounding chunks
|
this.lastItemPickupCheckTime = time;
|
||||||
|
|
||||||
|
final Chunk chunk = getChunk(); // TODO check surrounding chunks
|
||||||
final Set<Entity> entities = instance.getChunkEntities(chunk);
|
final Set<Entity> entities = instance.getChunkEntities(chunk);
|
||||||
for (Entity entity : entities) {
|
for (Entity entity : entities) {
|
||||||
if (entity instanceof ItemEntity) {
|
if (entity instanceof ItemEntity) {
|
||||||
@ -305,7 +313,10 @@ public abstract class LivingEntity extends Entity implements EquipmentHandler {
|
|||||||
soundCategory = SoundCategory.HOSTILE;
|
soundCategory = SoundCategory.HOSTILE;
|
||||||
}
|
}
|
||||||
|
|
||||||
SoundEffectPacket damageSoundPacket = SoundEffectPacket.create(soundCategory, sound, getPosition().getX(), getPosition().getY(), getPosition().getZ(), 1.0f, 1.0f);
|
SoundEffectPacket damageSoundPacket =
|
||||||
|
SoundEffectPacket.create(soundCategory, sound,
|
||||||
|
getPosition().getX(), getPosition().getY(), getPosition().getZ(),
|
||||||
|
1.0f, 1.0f);
|
||||||
sendPacketToViewersAndSelf(damageSoundPacket);
|
sendPacketToViewersAndSelf(damageSoundPacket);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -37,7 +37,6 @@ import net.minestom.server.network.packet.server.ServerPacket;
|
|||||||
import net.minestom.server.network.packet.server.play.*;
|
import net.minestom.server.network.packet.server.play.*;
|
||||||
import net.minestom.server.network.player.NettyPlayerConnection;
|
import net.minestom.server.network.player.NettyPlayerConnection;
|
||||||
import net.minestom.server.network.player.PlayerConnection;
|
import net.minestom.server.network.player.PlayerConnection;
|
||||||
import net.minestom.server.permission.Permission;
|
|
||||||
import net.minestom.server.recipe.Recipe;
|
import net.minestom.server.recipe.Recipe;
|
||||||
import net.minestom.server.recipe.RecipeManager;
|
import net.minestom.server.recipe.RecipeManager;
|
||||||
import net.minestom.server.resourcepack.ResourcePack;
|
import net.minestom.server.resourcepack.ResourcePack;
|
||||||
@ -55,6 +54,10 @@ import net.minestom.server.utils.callback.OptionalCallback;
|
|||||||
import net.minestom.server.utils.chunk.ChunkCallback;
|
import net.minestom.server.utils.chunk.ChunkCallback;
|
||||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||||
import net.minestom.server.utils.instance.InstanceUtils;
|
import net.minestom.server.utils.instance.InstanceUtils;
|
||||||
|
import net.minestom.server.utils.player.PlayerUtils;
|
||||||
|
import net.minestom.server.utils.time.CooldownUtils;
|
||||||
|
import net.minestom.server.utils.time.TimeUnit;
|
||||||
|
import net.minestom.server.utils.time.UpdateOption;
|
||||||
import net.minestom.server.utils.validate.Check;
|
import net.minestom.server.utils.validate.Check;
|
||||||
import net.minestom.server.world.DimensionType;
|
import net.minestom.server.world.DimensionType;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@ -126,6 +129,10 @@ public class Player extends LivingEntity implements CommandSender {
|
|||||||
private byte targetStage; // The current stage of the target block, only if multi player breaking is disabled
|
private byte targetStage; // The current stage of the target block, only if multi player breaking is disabled
|
||||||
private final Set<Player> targetBreakers = new HashSet<>(1); // Only used if multi player breaking is disabled, contains only this player
|
private final Set<Player> targetBreakers = new HashSet<>(1); // Only used if multi player breaking is disabled, contains only this player
|
||||||
|
|
||||||
|
// Experience orb pickup
|
||||||
|
protected UpdateOption experiencePickupCooldown = new UpdateOption(10, TimeUnit.TICK);
|
||||||
|
private long lastExperiencePickupCheckTime;
|
||||||
|
|
||||||
private BelowNameTag belowNameTag;
|
private BelowNameTag belowNameTag;
|
||||||
|
|
||||||
private int permissionLevel;
|
private int permissionLevel;
|
||||||
@ -148,8 +155,6 @@ public class Player extends LivingEntity implements CommandSender {
|
|||||||
// Tick related
|
// Tick related
|
||||||
private final PlayerTickEvent playerTickEvent = new PlayerTickEvent(this);
|
private final PlayerTickEvent playerTickEvent = new PlayerTickEvent(this);
|
||||||
|
|
||||||
private final List<Permission> permissions = new LinkedList<>();
|
|
||||||
|
|
||||||
public Player(UUID uuid, String username, PlayerConnection playerConnection) {
|
public Player(UUID uuid, String username, PlayerConnection playerConnection) {
|
||||||
super(EntityType.PLAYER);
|
super(EntityType.PLAYER);
|
||||||
this.uuid = uuid; // Override Entity#uuid defined in the constructor
|
this.uuid = uuid; // Override Entity#uuid defined in the constructor
|
||||||
@ -210,13 +215,13 @@ public class Player extends LivingEntity implements CommandSender {
|
|||||||
playerConnection.sendPacket(serverDifficultyPacket);
|
playerConnection.sendPacket(serverDifficultyPacket);
|
||||||
|
|
||||||
SpawnPositionPacket spawnPositionPacket = new SpawnPositionPacket();
|
SpawnPositionPacket spawnPositionPacket = new SpawnPositionPacket();
|
||||||
spawnPositionPacket.x = 0;
|
spawnPositionPacket.x = (int) respawnPoint.getX();
|
||||||
spawnPositionPacket.y = 0;
|
spawnPositionPacket.y = (int) respawnPoint.getY();
|
||||||
spawnPositionPacket.z = 0;
|
spawnPositionPacket.z = (int) respawnPoint.getZ();
|
||||||
playerConnection.sendPacket(spawnPositionPacket);
|
playerConnection.sendPacket(spawnPositionPacket);
|
||||||
|
|
||||||
// Add player to list with spawning skin
|
// Add player to list with spawning skin
|
||||||
PlayerSkinInitEvent skinInitEvent = new PlayerSkinInitEvent(this);
|
PlayerSkinInitEvent skinInitEvent = new PlayerSkinInitEvent(this, skin);
|
||||||
callEvent(PlayerSkinInitEvent.class, skinInitEvent);
|
callEvent(PlayerSkinInitEvent.class, skinInitEvent);
|
||||||
this.skin = skinInitEvent.getSkin();
|
this.skin = skinInitEvent.getSkin();
|
||||||
playerConnection.sendPacket(getAddPlayerToList());
|
playerConnection.sendPacket(getAddPlayerToList());
|
||||||
@ -291,9 +296,13 @@ public class Player extends LivingEntity implements CommandSender {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void update(long time) {
|
public void update(long time) {
|
||||||
// Flush all pending packets
|
|
||||||
playerConnection.flush();
|
|
||||||
|
|
||||||
|
// Flush all pending packets
|
||||||
|
if (PlayerUtils.isNettyClient(this)) {
|
||||||
|
((NettyPlayerConnection) playerConnection).getChannel().flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Network tick verification
|
||||||
playerConnection.updateStats();
|
playerConnection.updateStats();
|
||||||
|
|
||||||
// Process received packets
|
// Process received packets
|
||||||
@ -338,7 +347,8 @@ public class Player extends LivingEntity implements CommandSender {
|
|||||||
|
|
||||||
final Chunk chunk = instance.getChunkAt(targetBlockPosition);
|
final Chunk chunk = instance.getChunkAt(targetBlockPosition);
|
||||||
final int entityId = targetCustomBlock.getBreakEntityId(this);
|
final int entityId = targetCustomBlock.getBreakEntityId(this);
|
||||||
final BlockBreakAnimationPacket blockBreakAnimationPacket = new BlockBreakAnimationPacket(entityId, targetBlockPosition, targetStage);
|
final BlockBreakAnimationPacket blockBreakAnimationPacket =
|
||||||
|
new BlockBreakAnimationPacket(entityId, targetBlockPosition, targetStage);
|
||||||
Check.notNull(chunk, "Tried to interact with an unloaded chunk.");
|
Check.notNull(chunk, "Tried to interact with an unloaded chunk.");
|
||||||
chunk.sendPacketToViewers(blockBreakAnimationPacket);
|
chunk.sendPacketToViewers(blockBreakAnimationPacket);
|
||||||
|
|
||||||
@ -350,20 +360,24 @@ public class Player extends LivingEntity implements CommandSender {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Experience orb pickup
|
// Experience orb pickup
|
||||||
final Chunk chunk = instance.getChunkAt(getPosition()); // TODO check surrounding chunks
|
if (!CooldownUtils.hasCooldown(time, lastExperiencePickupCheckTime, experiencePickupCooldown)) {
|
||||||
final Set<Entity> entities = instance.getChunkEntities(chunk);
|
this.lastExperiencePickupCheckTime = time;
|
||||||
for (Entity entity : entities) {
|
|
||||||
if (entity instanceof ExperienceOrb) {
|
final Chunk chunk = getChunk(); // TODO check surrounding chunks
|
||||||
final ExperienceOrb experienceOrb = (ExperienceOrb) entity;
|
final Set<Entity> entities = instance.getChunkEntities(chunk);
|
||||||
final BoundingBox itemBoundingBox = experienceOrb.getBoundingBox();
|
for (Entity entity : entities) {
|
||||||
if (expandedBoundingBox.intersect(itemBoundingBox)) {
|
if (entity instanceof ExperienceOrb) {
|
||||||
if (experienceOrb.shouldRemove() || experienceOrb.isRemoveScheduled())
|
final ExperienceOrb experienceOrb = (ExperienceOrb) entity;
|
||||||
continue;
|
final BoundingBox itemBoundingBox = experienceOrb.getBoundingBox();
|
||||||
PickupExperienceEvent pickupExperienceEvent = new PickupExperienceEvent(experienceOrb);
|
if (expandedBoundingBox.intersect(itemBoundingBox)) {
|
||||||
callCancellableEvent(PickupExperienceEvent.class, pickupExperienceEvent, () -> {
|
if (experienceOrb.shouldRemove() || experienceOrb.isRemoveScheduled())
|
||||||
short experienceCount = pickupExperienceEvent.getExperienceCount(); // TODO give to player
|
continue;
|
||||||
entity.remove();
|
PickupExperienceEvent pickupExperienceEvent = new PickupExperienceEvent(experienceOrb);
|
||||||
});
|
callCancellableEvent(PickupExperienceEvent.class, pickupExperienceEvent, () -> {
|
||||||
|
short experienceCount = pickupExperienceEvent.getExperienceCount(); // TODO give to player
|
||||||
|
entity.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -414,11 +428,6 @@ public class Player extends LivingEntity implements CommandSender {
|
|||||||
entityPositionAndRotationPacket.pitch = position.getPitch();
|
entityPositionAndRotationPacket.pitch = position.getPitch();
|
||||||
entityPositionAndRotationPacket.onGround = onGround;
|
entityPositionAndRotationPacket.onGround = onGround;
|
||||||
|
|
||||||
lastX = position.getX();
|
|
||||||
lastY = position.getY();
|
|
||||||
lastZ = position.getZ();
|
|
||||||
lastYaw = position.getYaw();
|
|
||||||
lastPitch = position.getPitch();
|
|
||||||
updatePacket = entityPositionAndRotationPacket;
|
updatePacket = entityPositionAndRotationPacket;
|
||||||
} else if (positionChanged) {
|
} else if (positionChanged) {
|
||||||
EntityPositionPacket entityPositionPacket = new EntityPositionPacket();
|
EntityPositionPacket entityPositionPacket = new EntityPositionPacket();
|
||||||
@ -427,9 +436,7 @@ public class Player extends LivingEntity implements CommandSender {
|
|||||||
entityPositionPacket.deltaY = (short) ((position.getY() * 32 - lastY * 32) * 128);
|
entityPositionPacket.deltaY = (short) ((position.getY() * 32 - lastY * 32) * 128);
|
||||||
entityPositionPacket.deltaZ = (short) ((position.getZ() * 32 - lastZ * 32) * 128);
|
entityPositionPacket.deltaZ = (short) ((position.getZ() * 32 - lastZ * 32) * 128);
|
||||||
entityPositionPacket.onGround = onGround;
|
entityPositionPacket.onGround = onGround;
|
||||||
lastX = position.getX();
|
|
||||||
lastY = position.getY();
|
|
||||||
lastZ = position.getZ();
|
|
||||||
updatePacket = entityPositionPacket;
|
updatePacket = entityPositionPacket;
|
||||||
} else {
|
} else {
|
||||||
// View changed
|
// View changed
|
||||||
@ -439,12 +446,11 @@ public class Player extends LivingEntity implements CommandSender {
|
|||||||
entityRotationPacket.pitch = position.getPitch();
|
entityRotationPacket.pitch = position.getPitch();
|
||||||
entityRotationPacket.onGround = onGround;
|
entityRotationPacket.onGround = onGround;
|
||||||
|
|
||||||
lastYaw = position.getYaw();
|
|
||||||
lastPitch = position.getPitch();
|
|
||||||
updatePacket = entityRotationPacket;
|
updatePacket = entityRotationPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (viewChanged) {
|
if (viewChanged) {
|
||||||
|
// Yaw from the rotation packet seems to be ignored, which is why this is required
|
||||||
EntityHeadLookPacket entityHeadLookPacket = new EntityHeadLookPacket();
|
EntityHeadLookPacket entityHeadLookPacket = new EntityHeadLookPacket();
|
||||||
entityHeadLookPacket.entityId = getEntityId();
|
entityHeadLookPacket.entityId = getEntityId();
|
||||||
entityHeadLookPacket.yaw = position.getYaw();
|
entityHeadLookPacket.yaw = position.getYaw();
|
||||||
@ -466,11 +472,22 @@ public class Player extends LivingEntity implements CommandSender {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (positionChanged) {
|
||||||
|
lastX = position.getX();
|
||||||
|
lastY = position.getY();
|
||||||
|
lastZ = position.getZ();
|
||||||
|
}
|
||||||
|
if (viewChanged) {
|
||||||
|
lastYaw = position.getYaw();
|
||||||
|
lastPitch = position.getPitch();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void kill() {
|
public void kill() {
|
||||||
if (!isDead()) {
|
if (!isDead()) {
|
||||||
|
|
||||||
// send death screen text to the killed player
|
// send death screen text to the killed player
|
||||||
{
|
{
|
||||||
ColoredText deathText;
|
ColoredText deathText;
|
||||||
@ -608,6 +625,16 @@ public class Player extends LivingEntity implements CommandSender {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the player instance and load surrounding chunks if needed.
|
||||||
|
* <p>
|
||||||
|
* Be aware that because chunk operations are expensive,
|
||||||
|
* it is possible for this method to be non-blocking when retrieving chunks is required.
|
||||||
|
* <p>
|
||||||
|
* When this method is called for the first time (during player login), the player will be teleport at {@link #getRespawnPoint()}.
|
||||||
|
*
|
||||||
|
* @param instance the new instance of the player
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void setInstance(@NotNull Instance instance) {
|
public void setInstance(@NotNull Instance instance) {
|
||||||
Check.notNull(instance, "instance cannot be null!");
|
Check.notNull(instance, "instance cannot be null!");
|
||||||
@ -641,7 +668,8 @@ public class Player extends LivingEntity implements CommandSender {
|
|||||||
final ChunkCallback callback = (chunk) -> {
|
final ChunkCallback callback = (chunk) -> {
|
||||||
if (chunk != null) {
|
if (chunk != null) {
|
||||||
chunk.addViewer(this);
|
chunk.addViewer(this);
|
||||||
if (chunk.getChunkX() == Math.floorDiv((int) getPosition().getX(), 16) && chunk.getChunkZ() == Math.floorDiv((int) getPosition().getZ(), 16))
|
if (chunk.getChunkX() == ChunkUtils.getChunkCoordinate((int) getPosition().getX()) &&
|
||||||
|
chunk.getChunkZ() == ChunkUtils.getChunkCoordinate((int) getPosition().getZ()))
|
||||||
updateViewPosition(chunk);
|
updateViewPosition(chunk);
|
||||||
}
|
}
|
||||||
final boolean isLast = counter.get() == length - 1;
|
final boolean isLast = counter.get() == length - 1;
|
||||||
@ -674,6 +702,11 @@ public class Player extends LivingEntity implements CommandSender {
|
|||||||
private void spawnPlayer(Instance instance, boolean firstSpawn) {
|
private void spawnPlayer(Instance instance, boolean firstSpawn) {
|
||||||
this.viewableEntities.forEach(entity -> entity.removeViewer(this));
|
this.viewableEntities.forEach(entity -> entity.removeViewer(this));
|
||||||
super.setInstance(instance);
|
super.setInstance(instance);
|
||||||
|
|
||||||
|
if (firstSpawn) {
|
||||||
|
teleport(getRespawnPoint());
|
||||||
|
}
|
||||||
|
|
||||||
PlayerSpawnEvent spawnEvent = new PlayerSpawnEvent(this, instance, firstSpawn);
|
PlayerSpawnEvent spawnEvent = new PlayerSpawnEvent(this, instance, firstSpawn);
|
||||||
callEvent(PlayerSpawnEvent.class, spawnEvent);
|
callEvent(PlayerSpawnEvent.class, spawnEvent);
|
||||||
}
|
}
|
||||||
@ -736,12 +769,6 @@ public class Player extends LivingEntity implements CommandSender {
|
|||||||
sendMessage(ColoredText.of(message));
|
sendMessage(ColoredText.of(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
public Collection<Permission> getAllPermissions() {
|
|
||||||
return permissions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a message to the player.
|
* Sends a message to the player.
|
||||||
*
|
*
|
||||||
@ -1136,6 +1163,9 @@ public class Player extends LivingEntity implements CommandSender {
|
|||||||
public synchronized void setSkin(@Nullable PlayerSkin skin) {
|
public synchronized void setSkin(@Nullable PlayerSkin skin) {
|
||||||
this.skin = skin;
|
this.skin = skin;
|
||||||
|
|
||||||
|
if (instance == null)
|
||||||
|
return;
|
||||||
|
|
||||||
DestroyEntitiesPacket destroyEntitiesPacket = new DestroyEntitiesPacket();
|
DestroyEntitiesPacket destroyEntitiesPacket = new DestroyEntitiesPacket();
|
||||||
destroyEntitiesPacket.entityIds = new int[]{getEntityId()};
|
destroyEntitiesPacket.entityIds = new int[]{getEntityId()};
|
||||||
|
|
||||||
@ -1260,7 +1290,8 @@ public class Player extends LivingEntity implements CommandSender {
|
|||||||
facePosition(facePoint, entity.getPosition(), entity, targetPoint);
|
facePosition(facePoint, entity.getPosition(), entity, targetPoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void facePosition(@NotNull FacePoint facePoint, @NotNull Position targetPosition, @Nullable Entity entity, @Nullable FacePoint targetPoint) {
|
private void facePosition(@NotNull FacePoint facePoint, @NotNull Position targetPosition,
|
||||||
|
@Nullable Entity entity, @Nullable FacePoint targetPoint) {
|
||||||
FacePlayerPacket facePlayerPacket = new FacePlayerPacket();
|
FacePlayerPacket facePlayerPacket = new FacePlayerPacket();
|
||||||
facePlayerPacket.entityFacePosition = facePoint == FacePoint.EYE ?
|
facePlayerPacket.entityFacePosition = facePoint == FacePoint.EYE ?
|
||||||
FacePlayerPacket.FacePosition.EYES : FacePlayerPacket.FacePosition.FEET;
|
FacePlayerPacket.FacePosition.EYES : FacePlayerPacket.FacePosition.FEET;
|
||||||
@ -1687,7 +1718,7 @@ public class Player extends LivingEntity implements CommandSender {
|
|||||||
public void setTeam(Team team) {
|
public void setTeam(Team team) {
|
||||||
super.setTeam(team);
|
super.setTeam(team);
|
||||||
if (team != null)
|
if (team != null)
|
||||||
getPlayerConnection().sendPacket(team.getTeamsCreationPacket());
|
getPlayerConnection().sendPacket(team.createTeamsCreationPacket());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2194,7 +2225,8 @@ public class Player extends LivingEntity implements CommandSender {
|
|||||||
* @param targetBlockPosition the custom block position
|
* @param targetBlockPosition the custom block position
|
||||||
* @param breakers the breakers of the block, can be null if {@code this} is the only breaker
|
* @param breakers the breakers of the block, can be null if {@code this} is the only breaker
|
||||||
*/
|
*/
|
||||||
public void setTargetBlock(@NotNull CustomBlock targetCustomBlock, @NotNull BlockPosition targetBlockPosition, @Nullable Set<Player> breakers) {
|
public void setTargetBlock(@NotNull CustomBlock targetCustomBlock, @NotNull BlockPosition targetBlockPosition,
|
||||||
|
@Nullable Set<Player> breakers) {
|
||||||
this.targetCustomBlock = targetCustomBlock;
|
this.targetCustomBlock = targetCustomBlock;
|
||||||
this.targetBlockPosition = targetBlockPosition;
|
this.targetBlockPosition = targetBlockPosition;
|
||||||
|
|
||||||
@ -2338,7 +2370,7 @@ public class Player extends LivingEntity implements CommandSender {
|
|||||||
|
|
||||||
// Team
|
// Team
|
||||||
if (this.getTeam() != null)
|
if (this.getTeam() != null)
|
||||||
connection.sendPacket(this.getTeam().getTeamsCreationPacket());
|
connection.sendPacket(this.getTeam().createTeamsCreationPacket());
|
||||||
|
|
||||||
EntityHeadLookPacket entityHeadLookPacket = new EntityHeadLookPacket();
|
EntityHeadLookPacket entityHeadLookPacket = new EntityHeadLookPacket();
|
||||||
entityHeadLookPacket.entityId = getEntityId();
|
entityHeadLookPacket.entityId = getEntityId();
|
||||||
@ -2513,7 +2545,8 @@ public class Player extends LivingEntity implements CommandSender {
|
|||||||
* @param displayedSkinParts the player displayed skin parts
|
* @param displayedSkinParts the player displayed skin parts
|
||||||
* @param mainHand the player main hand
|
* @param mainHand the player main hand
|
||||||
*/
|
*/
|
||||||
public void refresh(String locale, byte viewDistance, ChatMode chatMode, boolean chatColors, byte displayedSkinParts, MainHand mainHand) {
|
public void refresh(String locale, byte viewDistance, ChatMode chatMode, boolean chatColors,
|
||||||
|
byte displayedSkinParts, MainHand mainHand) {
|
||||||
|
|
||||||
final boolean viewDistanceChanged = !firstRefresh && this.viewDistance != viewDistance;
|
final boolean viewDistanceChanged = !firstRefresh && this.viewDistance != viewDistance;
|
||||||
|
|
||||||
|
@ -60,6 +60,9 @@ public class FakePlayer extends Player {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Inits a new {@link FakePlayer} without adding it in cache.
|
* Inits a new {@link FakePlayer} without adding it in cache.
|
||||||
|
* <p>
|
||||||
|
* If you want the fake player to be obtainable with the {@link net.minestom.server.network.ConnectionManager}
|
||||||
|
* you need to specify it in a {@link FakePlayerOption} and use {@link #initPlayer(UUID, String, FakePlayerOption, Consumer)}.
|
||||||
*
|
*
|
||||||
* @param uuid the FakePlayer uuid
|
* @param uuid the FakePlayer uuid
|
||||||
* @param username the FakePlayer username
|
* @param username the FakePlayer username
|
||||||
|
@ -35,7 +35,7 @@ public class PlayerLoginEvent extends Event {
|
|||||||
* <p>
|
* <p>
|
||||||
* WARNING: this must NOT be null, otherwise the player cannot spawn.
|
* WARNING: this must NOT be null, otherwise the player cannot spawn.
|
||||||
*
|
*
|
||||||
* @return the spawning instance
|
* @return the spawning instance, null if not already defined
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public Instance getSpawningInstance() {
|
public Instance getSpawningInstance() {
|
||||||
|
@ -14,8 +14,9 @@ public class PlayerSkinInitEvent extends Event {
|
|||||||
private final Player player;
|
private final Player player;
|
||||||
private PlayerSkin skin;
|
private PlayerSkin skin;
|
||||||
|
|
||||||
public PlayerSkinInitEvent(@NotNull Player player) {
|
public PlayerSkinInitEvent(@NotNull Player player, @Nullable PlayerSkin currentSkin) {
|
||||||
this.player = player;
|
this.player = player;
|
||||||
|
this.skin = currentSkin;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
package net.minestom.server.extensions;
|
package net.minestom.server.extensions;
|
||||||
|
|
||||||
import com.google.gson.*;
|
import com.google.gson.*;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import net.minestom.server.extras.selfmodification.MinestomOverwriteClassLoader;
|
import net.minestom.server.extras.selfmodification.MinestomOverwriteClassLoader;
|
||||||
import net.minestom.server.utils.validate.Check;
|
import net.minestom.server.utils.validate.Check;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.spongepowered.asm.mixin.Mixins;
|
import org.spongepowered.asm.mixin.Mixins;
|
||||||
|
|
||||||
@ -19,9 +19,10 @@ import java.net.URLClassLoader;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.zip.ZipFile;
|
import java.util.zip.ZipFile;
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
public final class ExtensionManager {
|
public final class ExtensionManager {
|
||||||
|
|
||||||
|
public final static Logger LOGGER = LoggerFactory.getLogger(ExtensionManager.class);
|
||||||
|
|
||||||
private final static String INDEV_CLASSES_FOLDER = "minestom.extension.indevfolder.classes";
|
private final static String INDEV_CLASSES_FOLDER = "minestom.extension.indevfolder.classes";
|
||||||
private final static String INDEV_RESOURCES_FOLDER = "minestom.extension.indevfolder.resources";
|
private final static String INDEV_RESOURCES_FOLDER = "minestom.extension.indevfolder.resources";
|
||||||
private final static Gson GSON = new Gson();
|
private final static Gson GSON = new Gson();
|
||||||
@ -40,7 +41,7 @@ public final class ExtensionManager {
|
|||||||
|
|
||||||
if (!extensionFolder.exists()) {
|
if (!extensionFolder.exists()) {
|
||||||
if (!extensionFolder.mkdirs()) {
|
if (!extensionFolder.mkdirs()) {
|
||||||
log.error("Could not find or create the extension folder, extensions will not be loaded!");
|
LOGGER.error("Could not find or create the extension folder, extensions will not be loaded!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,7 +58,7 @@ public final class ExtensionManager {
|
|||||||
}
|
}
|
||||||
loader = newClassLoader(urls);
|
loader = newClassLoader(urls);
|
||||||
} catch (MalformedURLException e) {
|
} catch (MalformedURLException e) {
|
||||||
log.error("Failed to get URL.", e);
|
LOGGER.error("Failed to get URL.", e);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// TODO: Can't we use discoveredExtension.description here? Someone should test that.
|
// TODO: Can't we use discoveredExtension.description here? Someone should test that.
|
||||||
@ -71,7 +72,7 @@ public final class ExtensionManager {
|
|||||||
}
|
}
|
||||||
urlsString.append("'").append(url.toString()).append("'");
|
urlsString.append("'").append(url.toString()).append("'");
|
||||||
}
|
}
|
||||||
log.error("Failed to find extension.json in the urls '{}'.", urlsString);
|
LOGGER.error("Failed to find extension.json in the urls '{}'.", urlsString);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
JsonObject extensionDescriptionJson = JsonParser.parseReader(new InputStreamReader(extensionInputStream)).getAsJsonObject();
|
JsonObject extensionDescriptionJson = JsonParser.parseReader(new InputStreamReader(extensionInputStream)).getAsJsonObject();
|
||||||
@ -80,8 +81,8 @@ public final class ExtensionManager {
|
|||||||
final String extensionName = extensionDescriptionJson.get("name").getAsString();
|
final String extensionName = extensionDescriptionJson.get("name").getAsString();
|
||||||
// Check the validity of the extension's name.
|
// Check the validity of the extension's name.
|
||||||
if (!extensionName.matches("[A-Za-z]+")) {
|
if (!extensionName.matches("[A-Za-z]+")) {
|
||||||
log.error("Extension '{}' specified an invalid name.", extensionName);
|
LOGGER.error("Extension '{}' specified an invalid name.", extensionName);
|
||||||
log.error("Extension '{}' will not be loaded.", extensionName);
|
LOGGER.error("Extension '{}' will not be loaded.", extensionName);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,8 +91,8 @@ public final class ExtensionManager {
|
|||||||
{
|
{
|
||||||
String version;
|
String version;
|
||||||
if (!extensionDescriptionJson.has("version")) {
|
if (!extensionDescriptionJson.has("version")) {
|
||||||
log.warn("Extension '{}' did not specify a version.", extensionName);
|
LOGGER.warn("Extension '{}' did not specify a version.", extensionName);
|
||||||
log.warn("Extension '{}' will continue to load but should specify a plugin version.", extensionName);
|
LOGGER.warn("Extension '{}' will continue to load but should specify a plugin version.", extensionName);
|
||||||
version = "Not Specified";
|
version = "Not Specified";
|
||||||
} else {
|
} else {
|
||||||
version = extensionDescriptionJson.get("version").getAsString();
|
version = extensionDescriptionJson.get("version").getAsString();
|
||||||
@ -109,7 +110,7 @@ public final class ExtensionManager {
|
|||||||
extensionLoaders.put(extensionName.toLowerCase(), loader);
|
extensionLoaders.put(extensionName.toLowerCase(), loader);
|
||||||
|
|
||||||
if (extensions.containsKey(extensionName.toLowerCase())) {
|
if (extensions.containsKey(extensionName.toLowerCase())) {
|
||||||
log.error("An extension called '{}' has already been registered.", extensionName);
|
LOGGER.error("An extension called '{}' has already been registered.", extensionName);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,7 +118,7 @@ public final class ExtensionManager {
|
|||||||
try {
|
try {
|
||||||
jarClass = Class.forName(mainClass, true, loader);
|
jarClass = Class.forName(mainClass, true, loader);
|
||||||
} catch (ClassNotFoundException e) {
|
} catch (ClassNotFoundException e) {
|
||||||
log.error("Could not find main class '{}' in extension '{}'.", mainClass, extensionName, e);
|
LOGGER.error("Could not find main class '{}' in extension '{}'.", mainClass, extensionName, e);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,7 +126,7 @@ public final class ExtensionManager {
|
|||||||
try {
|
try {
|
||||||
extensionClass = jarClass.asSubclass(Extension.class);
|
extensionClass = jarClass.asSubclass(Extension.class);
|
||||||
} catch (ClassCastException e) {
|
} catch (ClassCastException e) {
|
||||||
log.error("Main class '{}' in '{}' does not extend the 'Extension' superclass.", mainClass, extensionName, e);
|
LOGGER.error("Main class '{}' in '{}' does not extend the 'Extension' superclass.", mainClass, extensionName, e);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,19 +136,19 @@ public final class ExtensionManager {
|
|||||||
// Let's just make it accessible, plugin creators don't have to make this public.
|
// Let's just make it accessible, plugin creators don't have to make this public.
|
||||||
constructor.setAccessible(true);
|
constructor.setAccessible(true);
|
||||||
} catch (NoSuchMethodException e) {
|
} catch (NoSuchMethodException e) {
|
||||||
log.error("Main class '{}' in '{}' does not define a no-args constructor.", mainClass, extensionName, e);
|
LOGGER.error("Main class '{}' in '{}' does not define a no-args constructor.", mainClass, extensionName, e);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Extension extension = null;
|
Extension extension = null;
|
||||||
try {
|
try {
|
||||||
extension = constructor.newInstance();
|
extension = constructor.newInstance();
|
||||||
} catch (InstantiationException e) {
|
} catch (InstantiationException e) {
|
||||||
log.error("Main class '{}' in '{}' cannot be an abstract class.", mainClass, extensionName, e);
|
LOGGER.error("Main class '{}' in '{}' cannot be an abstract class.", mainClass, extensionName, e);
|
||||||
continue;
|
continue;
|
||||||
} catch (IllegalAccessException ignored) {
|
} catch (IllegalAccessException ignored) {
|
||||||
// We made it accessible, should not occur
|
// We made it accessible, should not occur
|
||||||
} catch (InvocationTargetException e) {
|
} catch (InvocationTargetException e) {
|
||||||
log.error(
|
LOGGER.error(
|
||||||
"While instantiating the main class '{}' in '{}' an exception was thrown.",
|
"While instantiating the main class '{}' in '{}' an exception was thrown.",
|
||||||
mainClass,
|
mainClass,
|
||||||
extensionName,
|
extensionName,
|
||||||
@ -164,7 +165,7 @@ public final class ExtensionManager {
|
|||||||
} catch (IllegalAccessException e) {
|
} catch (IllegalAccessException e) {
|
||||||
// We made it accessible, should not occur
|
// We made it accessible, should not occur
|
||||||
} catch (NoSuchFieldException e) {
|
} catch (NoSuchFieldException e) {
|
||||||
log.error("Main class '{}' in '{}' has no description field.", mainClass, extensionName, e);
|
LOGGER.error("Main class '{}' in '{}' has no description field.", mainClass, extensionName, e);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,7 +179,7 @@ public final class ExtensionManager {
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
} catch (NoSuchFieldException e) {
|
} catch (NoSuchFieldException e) {
|
||||||
// This should also not occur (unless someone changed the logger in Extension superclass).
|
// This should also not occur (unless someone changed the logger in Extension superclass).
|
||||||
log.error("Main class '{}' in '{}' has no logger field.", mainClass, extensionName, e);
|
LOGGER.error("Main class '{}' in '{}' has no logger field.", mainClass, extensionName, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
extensions.put(extensionName.toLowerCase(), extension);
|
extensions.put(extensionName.toLowerCase(), extension);
|
||||||
@ -209,7 +210,7 @@ public final class ExtensionManager {
|
|||||||
|
|
||||||
// this allows developers to have their extension discovered while working on it, without having to build a jar and put in the extension folder
|
// this allows developers to have their extension discovered while working on it, without having to build a jar and put in the extension folder
|
||||||
if (System.getProperty(INDEV_CLASSES_FOLDER) != null && System.getProperty(INDEV_RESOURCES_FOLDER) != null) {
|
if (System.getProperty(INDEV_CLASSES_FOLDER) != null && System.getProperty(INDEV_RESOURCES_FOLDER) != null) {
|
||||||
log.info("Found indev folders for extension. Adding to list of discovered extensions.");
|
LOGGER.info("Found indev folders for extension. Adding to list of discovered extensions.");
|
||||||
final String extensionClasses = System.getProperty(INDEV_CLASSES_FOLDER);
|
final String extensionClasses = System.getProperty(INDEV_CLASSES_FOLDER);
|
||||||
final String extensionResources = System.getProperty(INDEV_RESOURCES_FOLDER);
|
final String extensionResources = System.getProperty(INDEV_RESOURCES_FOLDER);
|
||||||
try (InputStreamReader reader = new InputStreamReader(new FileInputStream(new File(extensionResources, "extension.json")))) {
|
try (InputStreamReader reader = new InputStreamReader(new FileInputStream(new File(extensionResources, "extension.json")))) {
|
||||||
@ -260,11 +261,11 @@ public final class ExtensionManager {
|
|||||||
private void setupCodeModifiers(@NotNull List<DiscoveredExtension> extensions) {
|
private void setupCodeModifiers(@NotNull List<DiscoveredExtension> extensions) {
|
||||||
final ClassLoader cl = getClass().getClassLoader();
|
final ClassLoader cl = getClass().getClassLoader();
|
||||||
if (!(cl instanceof MinestomOverwriteClassLoader)) {
|
if (!(cl instanceof MinestomOverwriteClassLoader)) {
|
||||||
log.warn("Current class loader is not a MinestomOverwriteClassLoader, but " + cl + ". This disables code modifiers (Mixin support is therefore disabled)");
|
LOGGER.warn("Current class loader is not a MinestomOverwriteClassLoader, but " + cl + ". This disables code modifiers (Mixin support is therefore disabled)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MinestomOverwriteClassLoader modifiableClassLoader = (MinestomOverwriteClassLoader) cl;
|
MinestomOverwriteClassLoader modifiableClassLoader = (MinestomOverwriteClassLoader) cl;
|
||||||
log.info("Start loading code modifiers...");
|
LOGGER.info("Start loading code modifiers...");
|
||||||
for (DiscoveredExtension extension : extensions) {
|
for (DiscoveredExtension extension : extensions) {
|
||||||
try {
|
try {
|
||||||
if (extension.description.has("codeModifiers")) {
|
if (extension.description.has("codeModifiers")) {
|
||||||
@ -276,14 +277,14 @@ public final class ExtensionManager {
|
|||||||
if (extension.description.has("mixinConfig")) {
|
if (extension.description.has("mixinConfig")) {
|
||||||
final String mixinConfigFile = extension.description.get("mixinConfig").getAsString();
|
final String mixinConfigFile = extension.description.get("mixinConfig").getAsString();
|
||||||
Mixins.addConfiguration(mixinConfigFile);
|
Mixins.addConfiguration(mixinConfigFile);
|
||||||
log.info("Found mixin in extension " + extension.description.get("name").getAsString() + ": " + mixinConfigFile);
|
LOGGER.info("Found mixin in extension " + extension.description.get("name").getAsString() + ": " + mixinConfigFile);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
log.error("Failed to load code modifier for extension in files: " + Arrays.toString(extension.files), e);
|
LOGGER.error("Failed to load code modifier for extension in files: " + Arrays.toString(extension.files), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.info("Done loading code modifiers.");
|
LOGGER.info("Done loading code modifiers.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class DiscoveredExtension {
|
private static class DiscoveredExtension {
|
||||||
|
@ -1,12 +1,21 @@
|
|||||||
package net.minestom.server.extras;
|
package net.minestom.server.extras;
|
||||||
|
|
||||||
import lombok.Getter;
|
import com.mojang.authlib.AuthenticationService;
|
||||||
|
import com.mojang.authlib.minecraft.MinecraftSessionService;
|
||||||
|
import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService;
|
||||||
import net.minestom.server.MinecraftServer;
|
import net.minestom.server.MinecraftServer;
|
||||||
|
import net.minestom.server.extras.mojangAuth.MojangCrypt;
|
||||||
|
|
||||||
|
import java.net.Proxy;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
|
||||||
public final class MojangAuth {
|
public final class MojangAuth {
|
||||||
|
|
||||||
@Getter
|
private static boolean enabled = false;
|
||||||
private static boolean usingMojangAuth = false;
|
|
||||||
|
private static final KeyPair keyPair = MojangCrypt.generateKeyPair();
|
||||||
|
private static final AuthenticationService authService = new YggdrasilAuthenticationService(Proxy.NO_PROXY, "");
|
||||||
|
private static final MinecraftSessionService sessionService = authService.createMinecraftSessionService();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enables mojang authentication on the server.
|
* Enables mojang authentication on the server.
|
||||||
@ -15,9 +24,25 @@ public final class MojangAuth {
|
|||||||
*/
|
*/
|
||||||
public static void init() {
|
public static void init() {
|
||||||
if (MinecraftServer.getNettyServer().getAddress() == null) {
|
if (MinecraftServer.getNettyServer().getAddress() == null) {
|
||||||
usingMojangAuth = true;
|
enabled = true;
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalStateException("The server has already been started");
|
throw new IllegalStateException("The server has already been started");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static KeyPair getKeyPair() {
|
||||||
|
return keyPair;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AuthenticationService getAuthService() {
|
||||||
|
return authService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MinecraftSessionService getSessionService() {
|
||||||
|
return sessionService;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
package net.minestom.server.extras.bungee;
|
package net.minestom.server.extras.bungee;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import net.minestom.server.entity.PlayerSkin;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BungeeCord forwarding support. This does not count as a security feature and you will still be required to manage your firewall.
|
* BungeeCord forwarding support. This does not count as a security feature and you will still be required to manage your firewall.
|
||||||
* <p>
|
* <p>
|
||||||
@ -7,7 +14,7 @@ package net.minestom.server.extras.bungee;
|
|||||||
*/
|
*/
|
||||||
public final class BungeeCordProxy {
|
public final class BungeeCordProxy {
|
||||||
|
|
||||||
private static boolean enabled;
|
private static volatile boolean enabled;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enables bungee IP forwarding.
|
* Enables bungee IP forwarding.
|
||||||
@ -24,4 +31,29 @@ public final class BungeeCordProxy {
|
|||||||
public static boolean isEnabled() {
|
public static boolean isEnabled() {
|
||||||
return enabled;
|
return enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static PlayerSkin readSkin(@NotNull String json) {
|
||||||
|
JsonArray array = JsonParser.parseString(json).getAsJsonArray();
|
||||||
|
String skinTexture = null;
|
||||||
|
String skinSignature = null;
|
||||||
|
|
||||||
|
for (JsonElement element : array) {
|
||||||
|
JsonObject jsonObject = element.getAsJsonObject();
|
||||||
|
final String name = jsonObject.get("name").getAsString();
|
||||||
|
final String value = jsonObject.get("value").getAsString();
|
||||||
|
final String signature = jsonObject.get("signature").getAsString();
|
||||||
|
|
||||||
|
if (name.equals("textures")) {
|
||||||
|
skinTexture = value;
|
||||||
|
skinSignature = signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skinTexture != null && skinSignature != null) {
|
||||||
|
return new PlayerSkin(skinTexture, skinSignature);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
package net.minestom.server.extras.optifine;
|
||||||
|
|
||||||
|
import net.minestom.server.MinecraftServer;
|
||||||
|
import net.minestom.server.utils.NamespaceID;
|
||||||
|
import net.minestom.server.utils.validate.Check;
|
||||||
|
import net.minestom.server.world.biomes.Biome;
|
||||||
|
import net.minestom.server.world.biomes.BiomeManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hacky class for Optifine because of an issue making the client crash if biomes 'swamp' or 'swamp_hills'
|
||||||
|
* are not registered.
|
||||||
|
* <p>
|
||||||
|
* Can be removed anytime, hope that it will be fixed.
|
||||||
|
*/
|
||||||
|
public final class OptifineSupport {
|
||||||
|
|
||||||
|
private static volatile boolean enabled;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables optifine support by registering the required biomes.
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException if optifine support is already enabled
|
||||||
|
*/
|
||||||
|
public static void enable() {
|
||||||
|
Check.stateCondition(enabled, "Optifine support is already enabled!");
|
||||||
|
OptifineSupport.enabled = true;
|
||||||
|
|
||||||
|
BiomeManager biomeManager = MinecraftServer.getBiomeManager();
|
||||||
|
biomeManager.addBiome(Biome.builder().name(NamespaceID.from("minecraft:swamp")).build());
|
||||||
|
biomeManager.addBiome(Biome.builder().name(NamespaceID.from("minecraft:swamp_hills")).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,10 +1,12 @@
|
|||||||
package net.minestom.server.extras.selfmodification;
|
package net.minestom.server.extras.selfmodification;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import net.minestom.server.MinecraftServer;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.objectweb.asm.ClassReader;
|
import org.objectweb.asm.ClassReader;
|
||||||
import org.objectweb.asm.ClassWriter;
|
import org.objectweb.asm.ClassWriter;
|
||||||
import org.objectweb.asm.tree.ClassNode;
|
import org.objectweb.asm.tree.ClassNode;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -20,9 +22,10 @@ import java.util.Set;
|
|||||||
/**
|
/**
|
||||||
* Class Loader that can modify class bytecode when they are loaded
|
* Class Loader that can modify class bytecode when they are loaded
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
|
||||||
public class MinestomOverwriteClassLoader extends URLClassLoader {
|
public class MinestomOverwriteClassLoader extends URLClassLoader {
|
||||||
|
|
||||||
|
public final static Logger LOGGER = LoggerFactory.getLogger(MinecraftServer.class);
|
||||||
|
|
||||||
private static MinestomOverwriteClassLoader INSTANCE;
|
private static MinestomOverwriteClassLoader INSTANCE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -105,18 +108,18 @@ public class MinestomOverwriteClassLoader extends URLClassLoader {
|
|||||||
try {
|
try {
|
||||||
// we do not load system classes by ourselves
|
// we do not load system classes by ourselves
|
||||||
Class<?> systemClass = ClassLoader.getPlatformClassLoader().loadClass(name);
|
Class<?> systemClass = ClassLoader.getPlatformClassLoader().loadClass(name);
|
||||||
log.trace("System class: " + systemClass);
|
LOGGER.trace("System class: " + systemClass);
|
||||||
return systemClass;
|
return systemClass;
|
||||||
} catch (ClassNotFoundException e) {
|
} catch (ClassNotFoundException e) {
|
||||||
try {
|
try {
|
||||||
if (isProtected(name)) {
|
if (isProtected(name)) {
|
||||||
log.trace("Protected: " + name);
|
LOGGER.trace("Protected: " + name);
|
||||||
return super.loadClass(name, resolve);
|
return super.loadClass(name, resolve);
|
||||||
}
|
}
|
||||||
|
|
||||||
return define(name, loadBytes(name, true), resolve);
|
return define(name, loadBytes(name, true), resolve);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
log.trace("Fail to load class, resorting to parent loader: " + name, ex);
|
LOGGER.trace("Fail to load class, resorting to parent loader: " + name, ex);
|
||||||
// fail to load class, let parent load
|
// fail to load class, let parent load
|
||||||
// this forbids code modification, but at least it will load
|
// this forbids code modification, but at least it will load
|
||||||
return super.loadClass(name, resolve);
|
return super.loadClass(name, resolve);
|
||||||
@ -137,7 +140,7 @@ public class MinestomOverwriteClassLoader extends URLClassLoader {
|
|||||||
|
|
||||||
private Class<?> define(String name, byte[] bytes, boolean resolve) {
|
private Class<?> define(String name, byte[] bytes, boolean resolve) {
|
||||||
Class<?> defined = defineClass(name, bytes, 0, bytes.length);
|
Class<?> defined = defineClass(name, bytes, 0, bytes.length);
|
||||||
log.trace("Loaded with code modifiers: " + name);
|
LOGGER.trace("Loaded with code modifiers: " + name);
|
||||||
if (resolve) {
|
if (resolve) {
|
||||||
resolveClass(defined);
|
resolveClass(defined);
|
||||||
}
|
}
|
||||||
@ -180,7 +183,7 @@ public class MinestomOverwriteClassLoader extends URLClassLoader {
|
|||||||
};
|
};
|
||||||
node.accept(writer);
|
node.accept(writer);
|
||||||
bytes = writer.toByteArray();
|
bytes = writer.toByteArray();
|
||||||
log.trace("Modified " + name);
|
LOGGER.trace("Modified " + name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return bytes;
|
return bytes;
|
||||||
@ -208,7 +211,7 @@ public class MinestomOverwriteClassLoader extends URLClassLoader {
|
|||||||
if (CodeModifier.class.isAssignableFrom(modifierClass)) {
|
if (CodeModifier.class.isAssignableFrom(modifierClass)) {
|
||||||
CodeModifier modifier = (CodeModifier) modifierClass.getDeclaredConstructor().newInstance();
|
CodeModifier modifier = (CodeModifier) modifierClass.getDeclaredConstructor().newInstance();
|
||||||
synchronized (modifiers) {
|
synchronized (modifiers) {
|
||||||
log.warn("Added Code modifier: " + modifier);
|
LOGGER.warn("Added Code modifier: " + modifier);
|
||||||
addCodeModifier(modifier);
|
addCodeModifier(modifier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,29 @@
|
|||||||
package net.minestom.server.extras.selfmodification.mixins;
|
package net.minestom.server.extras.selfmodification.mixins;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import net.minestom.server.MinecraftServer;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.spongepowered.asm.service.IMixinAuditTrail;
|
import org.spongepowered.asm.service.IMixinAuditTrail;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes care of logging mixin operations
|
* Takes care of logging mixin operations
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
|
||||||
public class MixinAuditTrailMinestom implements IMixinAuditTrail {
|
public class MixinAuditTrailMinestom implements IMixinAuditTrail {
|
||||||
|
|
||||||
|
public final static Logger LOGGER = LoggerFactory.getLogger(MinecraftServer.class);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onApply(String className, String mixinName) {
|
public void onApply(String className, String mixinName) {
|
||||||
log.trace("Applied mixin "+mixinName+" to class "+className);
|
LOGGER.trace("Applied mixin " + mixinName + " to class " + className);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPostProcess(String className) {
|
public void onPostProcess(String className) {
|
||||||
log.trace("Post processing "+className);
|
LOGGER.trace("Post processing " + className);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onGenerate(String className, String generatorName) {
|
public void onGenerate(String className, String generatorName) {
|
||||||
log.trace("Generating class "+className+" via generator "+generatorName);
|
LOGGER.trace("Generating class " + className + " via generator " + generatorName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package net.minestom.server.extras.velocity;
|
|||||||
|
|
||||||
import com.google.common.net.InetAddresses;
|
import com.google.common.net.InetAddresses;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import net.minestom.server.entity.PlayerSkin;
|
||||||
import net.minestom.server.utils.binary.BinaryReader;
|
import net.minestom.server.utils.binary.BinaryReader;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
@ -22,7 +23,7 @@ public final class VelocityProxy {
|
|||||||
public static final String PLAYER_INFO_CHANNEL = "velocity:player_info";
|
public static final String PLAYER_INFO_CHANNEL = "velocity:player_info";
|
||||||
private static final int SUPPORTED_FORWARDING_VERSION = 1;
|
private static final int SUPPORTED_FORWARDING_VERSION = 1;
|
||||||
|
|
||||||
private static boolean enabled;
|
private static volatile boolean enabled;
|
||||||
private static byte[] secret;
|
private static byte[] secret;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -72,7 +73,30 @@ public final class VelocityProxy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static InetAddress readAddress(@NotNull BinaryReader reader) {
|
public static InetAddress readAddress(@NotNull BinaryReader reader) {
|
||||||
return InetAddresses.forString(reader.readSizedString());
|
return InetAddresses.forString(reader.readSizedString(Integer.MAX_VALUE));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PlayerSkin readSkin(@NotNull BinaryReader reader) {
|
||||||
|
String skinTexture = null;
|
||||||
|
String skinSignature = null;
|
||||||
|
|
||||||
|
final int properties = reader.readVarInt();
|
||||||
|
for (int i1 = 0; i1 < properties; i1++) {
|
||||||
|
final String name = reader.readSizedString(Short.MAX_VALUE);
|
||||||
|
final String value = reader.readSizedString(Short.MAX_VALUE);
|
||||||
|
final String signature = reader.readBoolean() ? reader.readSizedString(Short.MAX_VALUE) : null;
|
||||||
|
|
||||||
|
if (name.equals("textures")) {
|
||||||
|
skinTexture = value;
|
||||||
|
skinSignature = signature;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skinTexture != null && skinSignature != null) {
|
||||||
|
return new PlayerSkin(skinTexture, skinSignature);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package net.minestom.server.instance;
|
package net.minestom.server.instance;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
import net.minestom.server.MinecraftServer;
|
import net.minestom.server.MinecraftServer;
|
||||||
import net.minestom.server.Viewable;
|
import net.minestom.server.Viewable;
|
||||||
import net.minestom.server.data.Data;
|
import net.minestom.server.data.Data;
|
||||||
@ -14,7 +13,6 @@ import net.minestom.server.instance.batch.ChunkBatch;
|
|||||||
import net.minestom.server.instance.block.Block;
|
import net.minestom.server.instance.block.Block;
|
||||||
import net.minestom.server.instance.block.BlockManager;
|
import net.minestom.server.instance.block.BlockManager;
|
||||||
import net.minestom.server.instance.block.CustomBlock;
|
import net.minestom.server.instance.block.CustomBlock;
|
||||||
import net.minestom.server.network.PacketWriterUtils;
|
|
||||||
import net.minestom.server.network.packet.server.play.ChunkDataPacket;
|
import net.minestom.server.network.packet.server.play.ChunkDataPacket;
|
||||||
import net.minestom.server.network.packet.server.play.UpdateLightPacket;
|
import net.minestom.server.network.packet.server.play.UpdateLightPacket;
|
||||||
import net.minestom.server.network.player.PlayerConnection;
|
import net.minestom.server.network.player.PlayerConnection;
|
||||||
@ -33,7 +31,6 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CopyOnWriteArraySet;
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
// TODO light data & API
|
// TODO light data & API
|
||||||
|
|
||||||
@ -44,7 +41,11 @@ import java.util.function.Consumer;
|
|||||||
* Chunks can be serialized using {@link #getSerializedData()} and deserialized back with {@link #readChunk(BinaryReader, ChunkCallback)},
|
* Chunks can be serialized using {@link #getSerializedData()} and deserialized back with {@link #readChunk(BinaryReader, ChunkCallback)},
|
||||||
* allowing you to implement your own storage solution if needed.
|
* allowing you to implement your own storage solution if needed.
|
||||||
* <p>
|
* <p>
|
||||||
* You can create your own implementation of this class by extending it and create the objects in {@link InstanceContainer#setChunkSupplier(ChunkSupplier)}.
|
* You can create your own implementation of this class by extending it
|
||||||
|
* and create the objects in {@link InstanceContainer#setChunkSupplier(ChunkSupplier)}.
|
||||||
|
* <p>
|
||||||
|
* You generally want to avoid storing references of this object as this could lead to a huge memory leak,
|
||||||
|
* you should store the chunk coordinates instead.
|
||||||
*/
|
*/
|
||||||
public abstract class Chunk implements Viewable, DataContainer {
|
public abstract class Chunk implements Viewable, DataContainer {
|
||||||
|
|
||||||
@ -68,11 +69,6 @@ public abstract class Chunk implements Viewable, DataContainer {
|
|||||||
private final boolean shouldGenerate;
|
private final boolean shouldGenerate;
|
||||||
private boolean readOnly;
|
private boolean readOnly;
|
||||||
|
|
||||||
// Packet cache
|
|
||||||
private volatile boolean enableCachePacket;
|
|
||||||
protected volatile boolean packetUpdated;
|
|
||||||
private ByteBuf fullDataPacket;
|
|
||||||
|
|
||||||
protected volatile boolean loaded = true;
|
protected volatile boolean loaded = true;
|
||||||
protected final Set<Player> viewers = new CopyOnWriteArraySet<>();
|
protected final Set<Player> viewers = new CopyOnWriteArraySet<>();
|
||||||
|
|
||||||
@ -87,9 +83,6 @@ public abstract class Chunk implements Viewable, DataContainer {
|
|||||||
this.chunkZ = chunkZ;
|
this.chunkZ = chunkZ;
|
||||||
this.shouldGenerate = shouldGenerate;
|
this.shouldGenerate = shouldGenerate;
|
||||||
|
|
||||||
// true by default
|
|
||||||
this.enableCachePacket = true;
|
|
||||||
|
|
||||||
if (biomes != null && biomes.length == BIOME_COUNT) {
|
if (biomes != null && biomes.length == BIOME_COUNT) {
|
||||||
this.biomes = biomes;
|
this.biomes = biomes;
|
||||||
} else {
|
} else {
|
||||||
@ -332,51 +325,6 @@ public abstract class Chunk implements Viewable, DataContainer {
|
|||||||
this.readOnly = readOnly;
|
this.readOnly = readOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets if this chunk automatically cache the latest {@link ChunkDataPacket} version.
|
|
||||||
* <p>
|
|
||||||
* Retrieved with {@link #retrieveDataBuffer(Consumer)}.
|
|
||||||
*
|
|
||||||
* @return true if the chunk automatically cache the chunk packet
|
|
||||||
*/
|
|
||||||
public boolean enableCachePacket() {
|
|
||||||
return enableCachePacket;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enables or disable the automatic {@link ChunkDataPacket} caching.
|
|
||||||
*
|
|
||||||
* @param enableCachePacket true to enable to chunk packet caching
|
|
||||||
*/
|
|
||||||
public synchronized void setEnableCachePacket(boolean enableCachePacket) {
|
|
||||||
this.enableCachePacket = enableCachePacket;
|
|
||||||
if (enableCachePacket && fullDataPacket != null) {
|
|
||||||
this.fullDataPacket.release();
|
|
||||||
this.fullDataPacket = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the cached data packet.
|
|
||||||
* <p>
|
|
||||||
* Use {@link #retrieveDataBuffer(Consumer)} to be sure to get the updated version.
|
|
||||||
*
|
|
||||||
* @return the last cached data packet, can be null or outdated
|
|
||||||
*/
|
|
||||||
public ByteBuf getFullDataPacket() {
|
|
||||||
return fullDataPacket;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the cached {@link ChunkDataPacket} of this chunk.
|
|
||||||
*
|
|
||||||
* @param fullDataPacket the new cached chunk packet
|
|
||||||
*/
|
|
||||||
public void setFullDataPacket(ByteBuf fullDataPacket) {
|
|
||||||
this.fullDataPacket = fullDataPacket;
|
|
||||||
this.packetUpdated = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes this chunk columnar space.
|
* Changes this chunk columnar space.
|
||||||
*
|
*
|
||||||
@ -386,27 +334,6 @@ public abstract class Chunk implements Viewable, DataContainer {
|
|||||||
this.columnarSpace = columnarSpace;
|
this.columnarSpace = columnarSpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves (and cache if needed) the updated data packet.
|
|
||||||
*
|
|
||||||
* @param consumer the consumer called once the packet is sure to be up-to-date
|
|
||||||
*/
|
|
||||||
public void retrieveDataBuffer(Consumer<ByteBuf> consumer) {
|
|
||||||
final ByteBuf data = getFullDataPacket();
|
|
||||||
if (data == null || !packetUpdated) {
|
|
||||||
// Packet has never been wrote or is outdated, write it
|
|
||||||
PacketWriterUtils.writeCallbackPacket(getFreshFullDataPacket(), packet -> {
|
|
||||||
if (enableCachePacket) {
|
|
||||||
setFullDataPacket(packet);
|
|
||||||
}
|
|
||||||
consumer.accept(packet);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Packet is up-to-date
|
|
||||||
consumer.accept(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a {@link ChunkDataPacket} which should contain the full chunk.
|
* Gets a {@link ChunkDataPacket} which should contain the full chunk.
|
||||||
*
|
*
|
||||||
@ -517,10 +444,10 @@ public abstract class Chunk implements Viewable, DataContainer {
|
|||||||
final PlayerConnection playerConnection = player.getPlayerConnection();
|
final PlayerConnection playerConnection = player.getPlayerConnection();
|
||||||
|
|
||||||
// Retrieve & send the buffer to the connection
|
// Retrieve & send the buffer to the connection
|
||||||
retrieveDataBuffer(buf -> playerConnection.sendPacket(buf, true));
|
playerConnection.sendPacket(getFreshFullDataPacket());
|
||||||
|
|
||||||
// TODO do not hardcode
|
// TODO do not hardcode light
|
||||||
if (MinecraftServer.isFixLighting()) {
|
{
|
||||||
UpdateLightPacket updateLightPacket = new UpdateLightPacket();
|
UpdateLightPacket updateLightPacket = new UpdateLightPacket();
|
||||||
updateLightPacket.chunkX = getChunkX();
|
updateLightPacket.chunkX = getChunkX();
|
||||||
updateLightPacket.chunkZ = getChunkZ();
|
updateLightPacket.chunkZ = getChunkZ();
|
||||||
@ -540,7 +467,8 @@ public abstract class Chunk implements Viewable, DataContainer {
|
|||||||
}
|
}
|
||||||
updateLightPacket.skyLight = temp;
|
updateLightPacket.skyLight = temp;
|
||||||
updateLightPacket.blockLight = temp2;
|
updateLightPacket.blockLight = temp2;
|
||||||
PacketWriterUtils.writeAndSend(player, updateLightPacket);
|
|
||||||
|
playerConnection.sendPacket(updateLightPacket);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -550,10 +478,8 @@ public abstract class Chunk implements Viewable, DataContainer {
|
|||||||
* @param player the player to update the chunk to
|
* @param player the player to update the chunk to
|
||||||
*/
|
*/
|
||||||
public void sendChunkUpdate(@NotNull Player player) {
|
public void sendChunkUpdate(@NotNull Player player) {
|
||||||
retrieveDataBuffer(buf -> {
|
final PlayerConnection playerConnection = player.getPlayerConnection();
|
||||||
final PlayerConnection playerConnection = player.getPlayerConnection();
|
playerConnection.sendPacket(getFreshFullDataPacket());
|
||||||
playerConnection.sendPacket(buf, true);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -562,14 +488,10 @@ public abstract class Chunk implements Viewable, DataContainer {
|
|||||||
public void sendChunkUpdate() {
|
public void sendChunkUpdate() {
|
||||||
final Set<Player> chunkViewers = getViewers();
|
final Set<Player> chunkViewers = getViewers();
|
||||||
if (!chunkViewers.isEmpty()) {
|
if (!chunkViewers.isEmpty()) {
|
||||||
retrieveDataBuffer(buf -> chunkViewers.forEach(player -> {
|
chunkViewers.forEach(player -> {
|
||||||
final PlayerConnection playerConnection = player.getPlayerConnection();
|
final PlayerConnection playerConnection = player.getPlayerConnection();
|
||||||
if (!PlayerUtils.isNettyClient(playerConnection))
|
playerConnection.sendPacket(getFreshFullDataPacket());
|
||||||
return;
|
});
|
||||||
|
|
||||||
playerConnection.sendPacket(buf, true);
|
|
||||||
}));
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -578,6 +500,7 @@ public abstract class Chunk implements Viewable, DataContainer {
|
|||||||
*
|
*
|
||||||
* @param section the section to update
|
* @param section the section to update
|
||||||
* @param player the player to send the packet to
|
* @param player the player to send the packet to
|
||||||
|
* @throws IllegalArgumentException if {@code section} is not a valid section
|
||||||
*/
|
*/
|
||||||
public void sendChunkSectionUpdate(int section, @NotNull Player player) {
|
public void sendChunkSectionUpdate(int section, @NotNull Player player) {
|
||||||
if (!PlayerUtils.isNettyClient(player))
|
if (!PlayerUtils.isNettyClient(player))
|
||||||
@ -585,7 +508,7 @@ public abstract class Chunk implements Viewable, DataContainer {
|
|||||||
Check.argCondition(!MathUtils.isBetween(section, 0, CHUNK_SECTION_COUNT),
|
Check.argCondition(!MathUtils.isBetween(section, 0, CHUNK_SECTION_COUNT),
|
||||||
"The chunk section " + section + " does not exist");
|
"The chunk section " + section + " does not exist");
|
||||||
|
|
||||||
PacketWriterUtils.writeAndSend(player, getChunkSectionUpdatePacket(section));
|
player.getPlayerConnection().sendPacket(createChunkSectionUpdatePacket(section));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -595,7 +518,7 @@ public abstract class Chunk implements Viewable, DataContainer {
|
|||||||
* @return the {@link ChunkDataPacket} to update a single chunk section
|
* @return the {@link ChunkDataPacket} to update a single chunk section
|
||||||
*/
|
*/
|
||||||
@NotNull
|
@NotNull
|
||||||
protected ChunkDataPacket getChunkSectionUpdatePacket(int section) {
|
protected ChunkDataPacket createChunkSectionUpdatePacket(int section) {
|
||||||
ChunkDataPacket chunkDataPacket = getFreshPartialDataPacket();
|
ChunkDataPacket chunkDataPacket = getFreshPartialDataPacket();
|
||||||
chunkDataPacket.fullChunk = false;
|
chunkDataPacket.fullChunk = false;
|
||||||
int[] sections = new int[CHUNK_SECTION_COUNT];
|
int[] sections = new int[CHUNK_SECTION_COUNT];
|
||||||
|
@ -10,10 +10,9 @@ import net.minestom.server.data.SerializableData;
|
|||||||
import net.minestom.server.data.SerializableDataImpl;
|
import net.minestom.server.data.SerializableDataImpl;
|
||||||
import net.minestom.server.entity.pathfinding.PFBlockDescription;
|
import net.minestom.server.entity.pathfinding.PFBlockDescription;
|
||||||
import net.minestom.server.instance.block.CustomBlock;
|
import net.minestom.server.instance.block.CustomBlock;
|
||||||
|
import net.minestom.server.instance.palette.PaletteStorage;
|
||||||
import net.minestom.server.network.packet.server.play.ChunkDataPacket;
|
import net.minestom.server.network.packet.server.play.ChunkDataPacket;
|
||||||
import net.minestom.server.utils.ArrayUtils;
|
|
||||||
import net.minestom.server.utils.BlockPosition;
|
import net.minestom.server.utils.BlockPosition;
|
||||||
import net.minestom.server.utils.MathUtils;
|
|
||||||
import net.minestom.server.utils.binary.BinaryReader;
|
import net.minestom.server.utils.binary.BinaryReader;
|
||||||
import net.minestom.server.utils.binary.BinaryWriter;
|
import net.minestom.server.utils.binary.BinaryWriter;
|
||||||
import net.minestom.server.utils.block.CustomBlockUtils;
|
import net.minestom.server.utils.block.CustomBlockUtils;
|
||||||
@ -42,11 +41,9 @@ public class DynamicChunk extends Chunk {
|
|||||||
*/
|
*/
|
||||||
private static final int DATA_FORMAT_VERSION = 1;
|
private static final int DATA_FORMAT_VERSION = 1;
|
||||||
|
|
||||||
// blocks id based on coordinate, see Chunk#getBlockIndex
|
// WARNING: not thread-safe and should not be changed
|
||||||
// WARNING: those arrays are NOT thread-safe
|
protected PaletteStorage blockPalette;
|
||||||
// and modifying them can cause issue with block data, update, block entity and the cached chunk packet
|
protected PaletteStorage customBlockPalette;
|
||||||
protected final short[] blocksStateId = new short[CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z];
|
|
||||||
protected final short[] customBlocksId = new short[CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z];
|
|
||||||
|
|
||||||
// Used to get all blocks with data (no null)
|
// Used to get all blocks with data (no null)
|
||||||
// Key is still chunk coordinates (see #getBlockIndex)
|
// Key is still chunk coordinates (see #getBlockIndex)
|
||||||
@ -60,8 +57,17 @@ public class DynamicChunk extends Chunk {
|
|||||||
// Block entities
|
// Block entities
|
||||||
protected final Set<Integer> blockEntities = new CopyOnWriteArraySet<>();
|
protected final Set<Integer> blockEntities = new CopyOnWriteArraySet<>();
|
||||||
|
|
||||||
public DynamicChunk(@Nullable Biome[] biomes, int chunkX, int chunkZ) {
|
public DynamicChunk(@Nullable Biome[] biomes, int chunkX, int chunkZ,
|
||||||
|
@NotNull PaletteStorage blockPalette, @NotNull PaletteStorage customBlockPalette) {
|
||||||
super(biomes, chunkX, chunkZ, true);
|
super(biomes, chunkX, chunkZ, true);
|
||||||
|
this.blockPalette = blockPalette;
|
||||||
|
this.customBlockPalette = customBlockPalette;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DynamicChunk(@Nullable Biome[] biomes, int chunkX, int chunkZ) {
|
||||||
|
this(biomes, chunkX, chunkZ,
|
||||||
|
new PaletteStorage(6, 2),
|
||||||
|
new PaletteStorage(6, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -79,14 +85,12 @@ public class DynamicChunk extends Chunk {
|
|||||||
final int index = getBlockIndex(x, y, z);
|
final int index = getBlockIndex(x, y, z);
|
||||||
// True if the block is not complete air without any custom block capabilities
|
// True if the block is not complete air without any custom block capabilities
|
||||||
final boolean hasBlock = blockStateId != 0 || customBlockId != 0;
|
final boolean hasBlock = blockStateId != 0 || customBlockId != 0;
|
||||||
if (hasBlock) {
|
|
||||||
this.blocksStateId[index] = blockStateId;
|
|
||||||
this.customBlocksId[index] = customBlockId;
|
|
||||||
} else {
|
|
||||||
// Block has been deleted, clear cache and return
|
|
||||||
|
|
||||||
this.blocksStateId[index] = 0; // Set to air
|
this.blockPalette.setBlockAt(x, y, z, blockStateId);
|
||||||
this.customBlocksId[index] = 0; // Remove custom block
|
this.customBlockPalette.setBlockAt(x, y, z, customBlockId);
|
||||||
|
|
||||||
|
if (!hasBlock) {
|
||||||
|
// Block has been deleted, clear cache and return
|
||||||
|
|
||||||
this.blocksData.remove(index);
|
this.blocksData.remove(index);
|
||||||
|
|
||||||
@ -94,8 +98,6 @@ public class DynamicChunk extends Chunk {
|
|||||||
this.updatableBlocksLastUpdate.remove(index);
|
this.updatableBlocksLastUpdate.remove(index);
|
||||||
|
|
||||||
this.blockEntities.remove(index);
|
this.blockEntities.remove(index);
|
||||||
|
|
||||||
this.packetUpdated = false;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,8 +123,6 @@ public class DynamicChunk extends Chunk {
|
|||||||
} else {
|
} else {
|
||||||
this.blockEntities.remove(index);
|
this.blockEntities.remove(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.packetUpdated = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -155,41 +155,23 @@ public class DynamicChunk extends Chunk {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public short getBlockStateId(int x, int y, int z) {
|
public short getBlockStateId(int x, int y, int z) {
|
||||||
final int index = getBlockIndex(x, y, z);
|
return this.blockPalette.getBlockAt(x, y, z);
|
||||||
if (!MathUtils.isBetween(index, 0, blocksStateId.length)) {
|
|
||||||
return 0; // TODO: custom invalid block
|
|
||||||
}
|
|
||||||
return blocksStateId[index];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public short getCustomBlockId(int x, int y, int z) {
|
public short getCustomBlockId(int x, int y, int z) {
|
||||||
final int index = getBlockIndex(x, y, z);
|
return customBlockPalette.getBlockAt(x, y, z);
|
||||||
if (!MathUtils.isBetween(index, 0, blocksStateId.length)) {
|
|
||||||
return 0; // TODO: custom invalid block
|
|
||||||
}
|
|
||||||
return customBlocksId[index];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void refreshBlockValue(int x, int y, int z, short blockStateId, short customBlockId) {
|
protected void refreshBlockValue(int x, int y, int z, short blockStateId, short customBlockId) {
|
||||||
final int blockIndex = getBlockIndex(x, y, z);
|
this.blockPalette.setBlockAt(x, y, z, blockStateId);
|
||||||
if (!MathUtils.isBetween(blockIndex, 0, blocksStateId.length)) {
|
this.customBlockPalette.setBlockAt(x, y, z, customBlockId);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.blocksStateId[blockIndex] = blockStateId;
|
|
||||||
this.customBlocksId[blockIndex] = customBlockId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void refreshBlockStateId(int x, int y, int z, short blockStateId) {
|
protected void refreshBlockStateId(int x, int y, int z, short blockStateId) {
|
||||||
final int blockIndex = getBlockIndex(x, y, z);
|
this.blockPalette.setBlockAt(x, y, z, blockStateId);
|
||||||
if (!MathUtils.isBetween(blockIndex, 0, blocksStateId.length)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.blocksStateId[blockIndex] = blockStateId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -260,8 +242,8 @@ public class DynamicChunk extends Chunk {
|
|||||||
for (byte z = 0; z < CHUNK_SIZE_Z; z++) {
|
for (byte z = 0; z < CHUNK_SIZE_Z; z++) {
|
||||||
final int index = getBlockIndex(x, y, z);
|
final int index = getBlockIndex(x, y, z);
|
||||||
|
|
||||||
final short blockStateId = blocksStateId[index];
|
final short blockStateId = blockPalette.getBlockAt(x, y, z);
|
||||||
final short customBlockId = customBlocksId[index];
|
final short customBlockId = customBlockPalette.getBlockAt(x, y, z);
|
||||||
|
|
||||||
// No block at the position
|
// No block at the position
|
||||||
if (blockStateId == 0 && customBlockId == 0)
|
if (blockStateId == 0 && customBlockId == 0)
|
||||||
@ -398,8 +380,8 @@ public class DynamicChunk extends Chunk {
|
|||||||
fullDataPacket.biomes = biomes.clone();
|
fullDataPacket.biomes = biomes.clone();
|
||||||
fullDataPacket.chunkX = chunkX;
|
fullDataPacket.chunkX = chunkX;
|
||||||
fullDataPacket.chunkZ = chunkZ;
|
fullDataPacket.chunkZ = chunkZ;
|
||||||
fullDataPacket.blocksStateId = blocksStateId.clone();
|
fullDataPacket.paletteStorage = blockPalette.copy();
|
||||||
fullDataPacket.customBlocksId = customBlocksId.clone();
|
fullDataPacket.customBlockPaletteStorage = customBlockPalette.copy();
|
||||||
fullDataPacket.blockEntities = new HashSet<>(blockEntities);
|
fullDataPacket.blockEntities = new HashSet<>(blockEntities);
|
||||||
fullDataPacket.blocksData = new Int2ObjectOpenHashMap<>(blocksData);
|
fullDataPacket.blocksData = new Int2ObjectOpenHashMap<>(blocksData);
|
||||||
return fullDataPacket;
|
return fullDataPacket;
|
||||||
@ -409,8 +391,8 @@ public class DynamicChunk extends Chunk {
|
|||||||
@Override
|
@Override
|
||||||
public Chunk copy(int chunkX, int chunkZ) {
|
public Chunk copy(int chunkX, int chunkZ) {
|
||||||
DynamicChunk dynamicChunk = new DynamicChunk(biomes.clone(), chunkX, chunkZ);
|
DynamicChunk dynamicChunk = new DynamicChunk(biomes.clone(), chunkX, chunkZ);
|
||||||
ArrayUtils.copyToDestination(blocksStateId, dynamicChunk.blocksStateId);
|
dynamicChunk.blockPalette = blockPalette.copy();
|
||||||
ArrayUtils.copyToDestination(customBlocksId, dynamicChunk.customBlocksId);
|
dynamicChunk.customBlockPalette = customBlockPalette.copy();
|
||||||
dynamicChunk.blocksData.putAll(blocksData);
|
dynamicChunk.blocksData.putAll(blocksData);
|
||||||
dynamicChunk.updatableBlocks.addAll(updatableBlocks);
|
dynamicChunk.updatableBlocks.addAll(updatableBlocks);
|
||||||
dynamicChunk.updatableBlocksLastUpdate.putAll(updatableBlocksLastUpdate);
|
dynamicChunk.updatableBlocksLastUpdate.putAll(updatableBlocksLastUpdate);
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
package net.minestom.server.instance;
|
package net.minestom.server.instance;
|
||||||
|
|
||||||
import net.minestom.server.instance.block.Block;
|
import net.minestom.server.instance.block.Block;
|
||||||
import net.minestom.server.network.PacketWriterUtils;
|
|
||||||
import net.minestom.server.network.packet.server.play.ExplosionPacket;
|
import net.minestom.server.network.packet.server.play.ExplosionPacket;
|
||||||
import net.minestom.server.utils.BlockPosition;
|
import net.minestom.server.utils.BlockPosition;
|
||||||
|
import net.minestom.server.utils.PacketUtils;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -54,7 +55,7 @@ public abstract class Explosion {
|
|||||||
*
|
*
|
||||||
* @param instance instance to perform this explosion in
|
* @param instance instance to perform this explosion in
|
||||||
*/
|
*/
|
||||||
public void apply(Instance instance) {
|
public void apply(@NotNull Instance instance) {
|
||||||
List<BlockPosition> blocks = prepare(instance);
|
List<BlockPosition> blocks = prepare(instance);
|
||||||
ExplosionPacket packet = new ExplosionPacket();
|
ExplosionPacket packet = new ExplosionPacket();
|
||||||
packet.x = getCenterX();
|
packet.x = getCenterX();
|
||||||
@ -80,7 +81,7 @@ public abstract class Explosion {
|
|||||||
postExplosion(instance, blocks, packet);
|
postExplosion(instance, blocks, packet);
|
||||||
|
|
||||||
// TODO send only to close players
|
// TODO send only to close players
|
||||||
PacketWriterUtils.writeAndSend(instance.getPlayers(), packet);
|
PacketUtils.sendGroupedPacket(instance.getPlayers(), packet);
|
||||||
|
|
||||||
postSend(instance, blocks);
|
postSend(instance, blocks);
|
||||||
}
|
}
|
||||||
|
@ -16,12 +16,12 @@ import net.minestom.server.instance.batch.ChunkBatch;
|
|||||||
import net.minestom.server.instance.block.Block;
|
import net.minestom.server.instance.block.Block;
|
||||||
import net.minestom.server.instance.block.BlockManager;
|
import net.minestom.server.instance.block.BlockManager;
|
||||||
import net.minestom.server.instance.block.CustomBlock;
|
import net.minestom.server.instance.block.CustomBlock;
|
||||||
import net.minestom.server.network.PacketWriterUtils;
|
|
||||||
import net.minestom.server.network.packet.server.play.BlockActionPacket;
|
import net.minestom.server.network.packet.server.play.BlockActionPacket;
|
||||||
import net.minestom.server.network.packet.server.play.TimeUpdatePacket;
|
import net.minestom.server.network.packet.server.play.TimeUpdatePacket;
|
||||||
import net.minestom.server.storage.StorageLocation;
|
import net.minestom.server.storage.StorageLocation;
|
||||||
import net.minestom.server.thread.ThreadProvider;
|
import net.minestom.server.thread.ThreadProvider;
|
||||||
import net.minestom.server.utils.BlockPosition;
|
import net.minestom.server.utils.BlockPosition;
|
||||||
|
import net.minestom.server.utils.PacketUtils;
|
||||||
import net.minestom.server.utils.Position;
|
import net.minestom.server.utils.Position;
|
||||||
import net.minestom.server.utils.chunk.ChunkCallback;
|
import net.minestom.server.utils.chunk.ChunkCallback;
|
||||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||||
@ -67,7 +67,7 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta
|
|||||||
// The time of the instance
|
// The time of the instance
|
||||||
private long time;
|
private long time;
|
||||||
private int timeRate = 1;
|
private int timeRate = 1;
|
||||||
private UpdateOption timeUpdate = new UpdateOption(1, TimeUnit.TICK);
|
private UpdateOption timeUpdate = new UpdateOption(1, TimeUnit.SECOND);
|
||||||
private long lastTimeUpdate;
|
private long lastTimeUpdate;
|
||||||
|
|
||||||
private final Map<Class<? extends Event>, Collection<EventCallback>> eventCallbacks = new ConcurrentHashMap<>();
|
private final Map<Class<? extends Event>, Collection<EventCallback>> eventCallbacks = new ConcurrentHashMap<>();
|
||||||
@ -111,7 +111,8 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Schedules a task to be run during the next instance tick.
|
* Schedules a task to be run during the next instance tick.
|
||||||
* It ensures that the task will be executed in the same thread as the instance and its chunks/entities (depending of the {@link ThreadProvider}).
|
* It ensures that the task will be executed in the same thread as the instance
|
||||||
|
* and its chunks/entities (depending of the {@link ThreadProvider}).
|
||||||
*
|
*
|
||||||
* @param callback the task to execute during the next instance tick
|
* @param callback the task to execute during the next instance tick
|
||||||
*/
|
*/
|
||||||
@ -369,7 +370,7 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta
|
|||||||
*/
|
*/
|
||||||
public void setTime(long time) {
|
public void setTime(long time) {
|
||||||
this.time = time;
|
this.time = time;
|
||||||
PacketWriterUtils.writeAndSend(getPlayers(), getTimePacket());
|
PacketUtils.sendGroupedPacket(getPlayers(), createTimePacket());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -417,12 +418,12 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a {@link TimeUpdatePacket} with the current age and time of this instance
|
* Creates a {@link TimeUpdatePacket} with the current age and time of this instance
|
||||||
*
|
*
|
||||||
* @return the {@link TimeUpdatePacket} with this instance data
|
* @return the {@link TimeUpdatePacket} with this instance data
|
||||||
*/
|
*/
|
||||||
@NotNull
|
@NotNull
|
||||||
private TimeUpdatePacket getTimePacket() {
|
private TimeUpdatePacket createTimePacket() {
|
||||||
TimeUpdatePacket timeUpdatePacket = new TimeUpdatePacket();
|
TimeUpdatePacket timeUpdatePacket = new TimeUpdatePacket();
|
||||||
timeUpdatePacket.worldAge = worldAge;
|
timeUpdatePacket.worldAge = worldAge;
|
||||||
timeUpdatePacket.timeOfDay = time;
|
timeUpdatePacket.timeOfDay = time;
|
||||||
@ -996,7 +997,7 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta
|
|||||||
|
|
||||||
// time needs to be send to players
|
// time needs to be send to players
|
||||||
if (timeUpdate != null && !CooldownUtils.hasCooldown(time, lastTimeUpdate, timeUpdate)) {
|
if (timeUpdate != null && !CooldownUtils.hasCooldown(time, lastTimeUpdate, timeUpdate)) {
|
||||||
PacketWriterUtils.writeAndSend(getPlayers(), getTimePacket());
|
PacketUtils.sendGroupedPacket(getPlayers(), createTimePacket());
|
||||||
this.lastTimeUpdate = time;
|
this.lastTimeUpdate = time;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -581,7 +581,7 @@ public class InstanceContainer extends Instance {
|
|||||||
* @throws NullPointerException if {@code chunkSupplier} is null
|
* @throws NullPointerException if {@code chunkSupplier} is null
|
||||||
*/
|
*/
|
||||||
public void setChunkSupplier(@NotNull ChunkSupplier chunkSupplier) {
|
public void setChunkSupplier(@NotNull ChunkSupplier chunkSupplier) {
|
||||||
Check.notNull(chunkSupplier, "The chunk supplier cannot be null, you can use a StaticChunk for a lightweight implementation");
|
Check.notNull(chunkSupplier, "The chunk supplier cannot be null!");
|
||||||
this.chunkSupplier = chunkSupplier;
|
this.chunkSupplier = chunkSupplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -605,6 +605,15 @@ public class InstanceContainer extends Instance {
|
|||||||
return Collections.unmodifiableList(sharedInstances);
|
return Collections.unmodifiableList(sharedInstances);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets if this instance has {@link SharedInstance} linked to it.
|
||||||
|
*
|
||||||
|
* @return true if {@link #getSharedInstances()} is not empty
|
||||||
|
*/
|
||||||
|
public boolean hasSharedInstances() {
|
||||||
|
return !sharedInstances.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assigns a {@link SharedInstance} to this container.
|
* Assigns a {@link SharedInstance} to this container.
|
||||||
* <p>
|
* <p>
|
||||||
@ -744,7 +753,7 @@ public class InstanceContainer extends Instance {
|
|||||||
* @param blockPosition the block position
|
* @param blockPosition the block position
|
||||||
* @param blockStateId the new state of the block
|
* @param blockStateId the new state of the block
|
||||||
*/
|
*/
|
||||||
private void sendBlockChange(Chunk chunk, BlockPosition blockPosition, short blockStateId) {
|
private void sendBlockChange(@NotNull Chunk chunk, @NotNull BlockPosition blockPosition, short blockStateId) {
|
||||||
BlockChangePacket blockChangePacket = new BlockChangePacket();
|
BlockChangePacket blockChangePacket = new BlockChangePacket();
|
||||||
blockChangePacket.blockPosition = blockPosition;
|
blockChangePacket.blockPosition = blockPosition;
|
||||||
blockChangePacket.blockStateId = blockStateId;
|
blockChangePacket.blockStateId = blockStateId;
|
||||||
|
@ -1,128 +0,0 @@
|
|||||||
package net.minestom.server.instance;
|
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
|
||||||
import net.minestom.server.data.Data;
|
|
||||||
import net.minestom.server.instance.block.BlockProvider;
|
|
||||||
import net.minestom.server.network.packet.server.play.ChunkDataPacket;
|
|
||||||
import net.minestom.server.utils.binary.BinaryReader;
|
|
||||||
import net.minestom.server.utils.callback.OptionalCallback;
|
|
||||||
import net.minestom.server.utils.chunk.ChunkCallback;
|
|
||||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
|
||||||
import net.minestom.server.world.biomes.Biome;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a {@link Chunk} which does not store any block, it makes use of a {@link BlockProvider}
|
|
||||||
* instead to use less memory.
|
|
||||||
* <p>
|
|
||||||
* Can be used for very simple chunks such as flat or others with not any random factor.
|
|
||||||
* <p>
|
|
||||||
* WARNING: adding blocks or anything to this chunk would not work, it is static.
|
|
||||||
*/
|
|
||||||
public class StaticChunk extends Chunk {
|
|
||||||
|
|
||||||
protected final BlockProvider blockProvider;
|
|
||||||
|
|
||||||
public StaticChunk(@Nullable Biome[] biomes, int chunkX, int chunkZ,
|
|
||||||
@NotNull BlockProvider blockProvider) {
|
|
||||||
super(biomes, chunkX, chunkZ, false);
|
|
||||||
this.blockProvider = blockProvider;
|
|
||||||
setReadOnly(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void UNSAFE_setBlock(int x, int y, int z, short blockStateId, short customBlockId, @Nullable Data data, boolean updatable) {
|
|
||||||
//noop
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void tick(long time, @NotNull Instance instance) {
|
|
||||||
//noop
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public short getBlockStateId(int x, int y, int z) {
|
|
||||||
return blockProvider.getBlockStateId(x, y, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public short getCustomBlockId(int x, int y, int z) {
|
|
||||||
//noop
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void refreshBlockValue(int x, int y, int z, short blockStateId, short customId) {
|
|
||||||
//noop
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void refreshBlockStateId(int x, int y, int z, short blockStateId) {
|
|
||||||
//noop
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Data getBlockData(int index) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setBlockData(int x, int y, int z, Data data) {
|
|
||||||
//noop
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
public Set<Integer> getBlockEntities() {
|
|
||||||
return new HashSet<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getSerializedData() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void readChunk(@NotNull BinaryReader reader, @Nullable ChunkCallback callback) {
|
|
||||||
OptionalCallback.execute(callback, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
protected ChunkDataPacket createFreshPacket() {
|
|
||||||
ChunkDataPacket fullDataPacket = new ChunkDataPacket();
|
|
||||||
fullDataPacket.biomes = biomes.clone();
|
|
||||||
fullDataPacket.chunkX = chunkX;
|
|
||||||
fullDataPacket.chunkZ = chunkZ;
|
|
||||||
short[] blocksStateId = new short[CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z];
|
|
||||||
for (int i = 0; i < blocksStateId.length; i++) {
|
|
||||||
final int x = ChunkUtils.blockIndexToChunkPositionX(i);
|
|
||||||
final int y = ChunkUtils.blockIndexToChunkPositionY(i);
|
|
||||||
final int z = ChunkUtils.blockIndexToChunkPositionZ(i);
|
|
||||||
blocksStateId[i] = blockProvider.getBlockStateId(x, y, z);
|
|
||||||
}
|
|
||||||
fullDataPacket.blocksStateId = blocksStateId;
|
|
||||||
fullDataPacket.customBlocksId = new short[0];
|
|
||||||
fullDataPacket.blockEntities = new HashSet<>();
|
|
||||||
fullDataPacket.blocksData = new Int2ObjectOpenHashMap<>();
|
|
||||||
return fullDataPacket;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
public Chunk copy(int chunkX, int chunkZ) {
|
|
||||||
StaticChunk staticChunk = new StaticChunk(biomes.clone(), chunkX, chunkZ, blockProvider);
|
|
||||||
// Prevent re-writing the whole packet since it is static anyway
|
|
||||||
final ByteBuf packetBuffer = getFullDataPacket();
|
|
||||||
if (packetBuffer != null) {
|
|
||||||
staticChunk.setFullDataPacket(packetBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
return staticChunk;
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,8 +2,8 @@ package net.minestom.server.instance;
|
|||||||
|
|
||||||
import net.minestom.server.entity.Entity;
|
import net.minestom.server.entity.Entity;
|
||||||
import net.minestom.server.entity.Player;
|
import net.minestom.server.entity.Player;
|
||||||
import net.minestom.server.network.PacketWriterUtils;
|
|
||||||
import net.minestom.server.network.packet.server.play.WorldBorderPacket;
|
import net.minestom.server.network.packet.server.play.WorldBorderPacket;
|
||||||
|
import net.minestom.server.utils.PacketUtils;
|
||||||
import net.minestom.server.utils.Position;
|
import net.minestom.server.utils.Position;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
@ -100,6 +100,7 @@ public class WorldBorder {
|
|||||||
*/
|
*/
|
||||||
public void setWarningTime(int warningTime) {
|
public void setWarningTime(int warningTime) {
|
||||||
this.warningTime = warningTime;
|
this.warningTime = warningTime;
|
||||||
|
|
||||||
WorldBorderPacket worldBorderPacket = new WorldBorderPacket();
|
WorldBorderPacket worldBorderPacket = new WorldBorderPacket();
|
||||||
worldBorderPacket.action = WorldBorderPacket.Action.SET_WARNING_TIME;
|
worldBorderPacket.action = WorldBorderPacket.Action.SET_WARNING_TIME;
|
||||||
worldBorderPacket.wbAction = new WorldBorderPacket.WBSetWarningTime(warningTime);
|
worldBorderPacket.wbAction = new WorldBorderPacket.WBSetWarningTime(warningTime);
|
||||||
@ -115,6 +116,7 @@ public class WorldBorder {
|
|||||||
*/
|
*/
|
||||||
public void setWarningBlocks(int warningBlocks) {
|
public void setWarningBlocks(int warningBlocks) {
|
||||||
this.warningBlocks = warningBlocks;
|
this.warningBlocks = warningBlocks;
|
||||||
|
|
||||||
WorldBorderPacket worldBorderPacket = new WorldBorderPacket();
|
WorldBorderPacket worldBorderPacket = new WorldBorderPacket();
|
||||||
worldBorderPacket.action = WorldBorderPacket.Action.SET_WARNING_BLOCKS;
|
worldBorderPacket.action = WorldBorderPacket.Action.SET_WARNING_BLOCKS;
|
||||||
worldBorderPacket.wbAction = new WorldBorderPacket.WBSetWarningBlocks(warningBlocks);
|
worldBorderPacket.wbAction = new WorldBorderPacket.WBSetWarningBlocks(warningBlocks);
|
||||||
@ -137,6 +139,7 @@ public class WorldBorder {
|
|||||||
this.speed = speed;
|
this.speed = speed;
|
||||||
this.lerpStartTime = System.currentTimeMillis();
|
this.lerpStartTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
|
||||||
WorldBorderPacket worldBorderPacket = new WorldBorderPacket();
|
WorldBorderPacket worldBorderPacket = new WorldBorderPacket();
|
||||||
worldBorderPacket.action = WorldBorderPacket.Action.LERP_SIZE;
|
worldBorderPacket.action = WorldBorderPacket.Action.LERP_SIZE;
|
||||||
worldBorderPacket.wbAction = new WorldBorderPacket.WBLerpSize(oldDiameter, newDiameter, speed);
|
worldBorderPacket.wbAction = new WorldBorderPacket.WBLerpSize(oldDiameter, newDiameter, speed);
|
||||||
@ -270,10 +273,10 @@ public class WorldBorder {
|
|||||||
/**
|
/**
|
||||||
* Sends a {@link WorldBorderPacket} to all the instance players.
|
* Sends a {@link WorldBorderPacket} to all the instance players.
|
||||||
*
|
*
|
||||||
* @param worldBorderPacket the packet to send
|
* @param packet the packet to send
|
||||||
*/
|
*/
|
||||||
private void sendPacket(@NotNull WorldBorderPacket worldBorderPacket) {
|
private void sendPacket(@NotNull WorldBorderPacket packet) {
|
||||||
PacketWriterUtils.writeAndSend(instance.getPlayers(), worldBorderPacket);
|
PacketUtils.sendGroupedPacket(instance.getPlayers(), packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum CollisionAxis {
|
public enum CollisionAxis {
|
||||||
|
@ -1,11 +1,18 @@
|
|||||||
package net.minestom.server.instance.batch;
|
package net.minestom.server.instance.batch;
|
||||||
|
|
||||||
import kotlin.collections.ArrayDeque;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||||
|
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||||
|
import it.unimi.dsi.fastutil.longs.LongList;
|
||||||
import net.minestom.server.data.Data;
|
import net.minestom.server.data.Data;
|
||||||
import net.minestom.server.instance.*;
|
import net.minestom.server.instance.*;
|
||||||
import net.minestom.server.instance.block.CustomBlock;
|
import net.minestom.server.instance.block.CustomBlock;
|
||||||
import net.minestom.server.utils.block.CustomBlockUtils;
|
import net.minestom.server.utils.block.CustomBlockUtils;
|
||||||
|
import net.minestom.server.utils.callback.OptionalCallback;
|
||||||
import net.minestom.server.utils.chunk.ChunkCallback;
|
import net.minestom.server.utils.chunk.ChunkCallback;
|
||||||
|
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||||
|
import net.minestom.server.utils.validate.Check;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -21,49 +28,76 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class ChunkBatch implements InstanceBatch {
|
public class ChunkBatch implements InstanceBatch {
|
||||||
|
|
||||||
private static final int INITIAL_SIZE = (Chunk.CHUNK_SIZE_X * Chunk.CHUNK_SIZE_Y * Chunk.CHUNK_SIZE_Z) / 2;
|
|
||||||
|
|
||||||
private final InstanceContainer instance;
|
private final InstanceContainer instance;
|
||||||
private final Chunk chunk;
|
private final Chunk chunk;
|
||||||
|
|
||||||
// Need to be synchronized manually
|
// Need to be synchronized manually
|
||||||
private final ArrayDeque<BlockData> dataList = new ArrayDeque<>(INITIAL_SIZE);
|
// Format: blockIndex/blockStateId/customBlockId (32/16/16 bits)
|
||||||
|
private final LongList blocks = new LongArrayList();
|
||||||
|
|
||||||
public ChunkBatch(InstanceContainer instance, Chunk chunk) {
|
// Need to be synchronized manually
|
||||||
|
// block index - data
|
||||||
|
private final Int2ObjectMap<Data> blockDataMap = new Int2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
|
public ChunkBatch(@NotNull InstanceContainer instance, @NotNull Chunk chunk) {
|
||||||
this.instance = instance;
|
this.instance = instance;
|
||||||
this.chunk = chunk;
|
this.chunk = chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setBlockStateId(int x, int y, int z, short blockStateId, Data data) {
|
public void setBlockStateId(int x, int y, int z, short blockStateId, @Nullable Data data) {
|
||||||
addBlockData((byte) x, y, (byte) z, blockStateId, (short) 0, data);
|
addBlockData((byte) x, y, (byte) z, blockStateId, (short) 0, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setCustomBlock(int x, int y, int z, short customBlockId, Data data) {
|
public void setCustomBlock(int x, int y, int z, short customBlockId, @Nullable Data data) {
|
||||||
final CustomBlock customBlock = BLOCK_MANAGER.getCustomBlock(customBlockId);
|
final CustomBlock customBlock = BLOCK_MANAGER.getCustomBlock(customBlockId);
|
||||||
|
Check.notNull(customBlock, "The custom block with the id " + customBlockId + " does not exist!");
|
||||||
addBlockData((byte) x, y, (byte) z, customBlock.getDefaultBlockStateId(), customBlockId, data);
|
addBlockData((byte) x, y, (byte) z, customBlock.getDefaultBlockStateId(), customBlockId, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setSeparateBlocks(int x, int y, int z, short blockStateId, short customBlockId, Data data) {
|
public void setSeparateBlocks(int x, int y, int z, short blockStateId, short customBlockId, @Nullable Data data) {
|
||||||
addBlockData((byte) x, y, (byte) z, blockStateId, customBlockId, data);
|
addBlockData((byte) x, y, (byte) z, blockStateId, customBlockId, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addBlockData(byte x, int y, byte z, short blockStateId, short customBlockId, Data data) {
|
private void addBlockData(byte x, int y, byte z, short blockStateId, short customBlockId, @Nullable Data data) {
|
||||||
// TODO store a single long with bitwise operators (xyz;boolean,short,short,boolean) with the data in a map
|
final int index = ChunkUtils.getBlockIndex(x, y, z);
|
||||||
final BlockData blockData = new BlockData(x, y, z, blockStateId, customBlockId, data);
|
|
||||||
synchronized (dataList) {
|
if (data != null) {
|
||||||
this.dataList.add(blockData);
|
synchronized (blockDataMap) {
|
||||||
|
this.blockDataMap.put(index, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long value = index;
|
||||||
|
value = (value << 16) | blockStateId;
|
||||||
|
value = (value << 16) | customBlockId;
|
||||||
|
|
||||||
|
synchronized (blocks) {
|
||||||
|
this.blocks.add(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void flushChunkGenerator(ChunkGenerator chunkGenerator, @Nullable ChunkCallback callback) {
|
/**
|
||||||
|
* Called to fill the chunk batch.
|
||||||
|
*
|
||||||
|
* @param chunkGenerator the chunk generator
|
||||||
|
* @param callback the optional callback executed once the batch is done
|
||||||
|
*/
|
||||||
|
public void flushChunkGenerator(@NotNull ChunkGenerator chunkGenerator, @Nullable ChunkCallback callback) {
|
||||||
BLOCK_BATCH_POOL.execute(() -> {
|
BLOCK_BATCH_POOL.execute(() -> {
|
||||||
final List<ChunkPopulator> populators = chunkGenerator.getPopulators();
|
final List<ChunkPopulator> populators = chunkGenerator.getPopulators();
|
||||||
final boolean hasPopulator = populators != null && !populators.isEmpty();
|
final boolean hasPopulator = populators != null && !populators.isEmpty();
|
||||||
|
|
||||||
chunkGenerator.generateChunkData(this, chunk.getChunkX(), chunk.getChunkZ());
|
chunkGenerator.generateChunkData(this, chunk.getChunkX(), chunk.getChunkZ());
|
||||||
|
|
||||||
|
// Check if there is anything to process
|
||||||
|
if (blocks.isEmpty() && !hasPopulator) {
|
||||||
|
OptionalCallback.execute(callback, chunk);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
singleThreadFlush(hasPopulator ? null : callback, true);
|
singleThreadFlush(hasPopulator ? null : callback, true);
|
||||||
|
|
||||||
clearData(); // So the populators won't place those blocks again
|
clearData(); // So the populators won't place those blocks again
|
||||||
@ -104,8 +138,8 @@ public class ChunkBatch implements InstanceBatch {
|
|||||||
* Resets the chunk batch by removing all the entries.
|
* Resets the chunk batch by removing all the entries.
|
||||||
*/
|
*/
|
||||||
public void clearData() {
|
public void clearData() {
|
||||||
synchronized (dataList) {
|
synchronized (blocks) {
|
||||||
this.dataList.clear();
|
this.blocks.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,13 +150,18 @@ public class ChunkBatch implements InstanceBatch {
|
|||||||
* @param safeCallback true to run the callback in the instance update thread, otherwise run in the current one
|
* @param safeCallback true to run the callback in the instance update thread, otherwise run in the current one
|
||||||
*/
|
*/
|
||||||
private void singleThreadFlush(@Nullable ChunkCallback callback, boolean safeCallback) {
|
private void singleThreadFlush(@Nullable ChunkCallback callback, boolean safeCallback) {
|
||||||
|
if (blocks.isEmpty()) {
|
||||||
|
OptionalCallback.execute(callback, chunk);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
synchronized (chunk) {
|
synchronized (chunk) {
|
||||||
if (!chunk.isLoaded())
|
if (!chunk.isLoaded())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
synchronized (dataList) {
|
synchronized (blocks) {
|
||||||
for (BlockData data : dataList) {
|
for (long block : blocks) {
|
||||||
data.apply(chunk);
|
apply(chunk, block);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,25 +180,29 @@ public class ChunkBatch implements InstanceBatch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class BlockData {
|
/**
|
||||||
|
* Places a block which is encoded in a long.
|
||||||
|
*
|
||||||
|
* @param chunk the chunk to place the block on
|
||||||
|
* @param value the block data
|
||||||
|
*/
|
||||||
|
private void apply(@NotNull Chunk chunk, long value) {
|
||||||
|
final short customBlockId = (short) (value & 0xFF);
|
||||||
|
final short blockId = (short) (value >> 16 & 0xFF);
|
||||||
|
final int index = (int) (value >> 32 & 0xFFFF);
|
||||||
|
|
||||||
private final int x, y, z;
|
Data data = null;
|
||||||
private final short blockStateId;
|
if (!blockDataMap.isEmpty()) {
|
||||||
private final short customBlockId;
|
synchronized (blockDataMap) {
|
||||||
private final Data data;
|
data = blockDataMap.get(index);
|
||||||
|
}
|
||||||
private BlockData(int x, int y, int z, short blockStateId, short customBlockId, @Nullable Data data) {
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
this.z = z;
|
|
||||||
this.blockStateId = blockStateId;
|
|
||||||
this.customBlockId = customBlockId;
|
|
||||||
this.data = data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void apply(Chunk chunk) {
|
chunk.UNSAFE_setBlock(ChunkUtils.blockIndexToChunkPositionX(index),
|
||||||
chunk.UNSAFE_setBlock(x, y, z, blockStateId, customBlockId, data, CustomBlockUtils.hasUpdate(customBlockId));
|
ChunkUtils.blockIndexToChunkPositionY(index),
|
||||||
}
|
ChunkUtils.blockIndexToChunkPositionZ(index),
|
||||||
|
blockId, customBlockId, data, CustomBlockUtils.hasUpdate(customBlockId));
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ public class BlockManager {
|
|||||||
* @throws IllegalArgumentException if <code>customBlock</code> block id is not greater than 0
|
* @throws IllegalArgumentException if <code>customBlock</code> block id is not greater than 0
|
||||||
* @throws IllegalStateException if the id of <code>customBlock</code> is already registered
|
* @throws IllegalStateException if the id of <code>customBlock</code> is already registered
|
||||||
*/
|
*/
|
||||||
public void registerCustomBlock(@NotNull CustomBlock customBlock) {
|
public synchronized void registerCustomBlock(@NotNull CustomBlock customBlock) {
|
||||||
final short id = customBlock.getCustomBlockId();
|
final short id = customBlock.getCustomBlockId();
|
||||||
Check.argCondition(id <= 0, "Custom block ID must be greater than 0, got: " + id);
|
Check.argCondition(id <= 0, "Custom block ID must be greater than 0, got: " + id);
|
||||||
Check.stateCondition(customBlocksInternalId[id] != null, "a CustomBlock with the id " + id + " already exists");
|
Check.stateCondition(customBlocksInternalId[id] != null, "a CustomBlock with the id " + id + " already exists");
|
||||||
@ -41,7 +41,7 @@ public class BlockManager {
|
|||||||
* @param blockPlacementRule the block placement rule to register
|
* @param blockPlacementRule the block placement rule to register
|
||||||
* @throws IllegalArgumentException if <code>blockPlacementRule</code> block id is negative
|
* @throws IllegalArgumentException if <code>blockPlacementRule</code> block id is negative
|
||||||
*/
|
*/
|
||||||
public void registerBlockPlacementRule(@NotNull BlockPlacementRule blockPlacementRule) {
|
public synchronized void registerBlockPlacementRule(@NotNull BlockPlacementRule blockPlacementRule) {
|
||||||
final short id = blockPlacementRule.getBlockId();
|
final short id = blockPlacementRule.getBlockId();
|
||||||
Check.argCondition(id < 0, "Block ID must be >= 0, got: " + id);
|
Check.argCondition(id < 0, "Block ID must be >= 0, got: " + id);
|
||||||
|
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
package net.minestom.server.instance.block;
|
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface BlockProvider {
|
|
||||||
short getBlockStateId(int x, int y, int z);
|
|
||||||
}
|
|
@ -0,0 +1,373 @@
|
|||||||
|
package net.minestom.server.instance.palette;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.shorts.Short2ShortLinkedOpenHashMap;
|
||||||
|
import it.unimi.dsi.fastutil.shorts.Short2ShortOpenHashMap;
|
||||||
|
import net.minestom.server.instance.Chunk;
|
||||||
|
import net.minestom.server.utils.MathUtils;
|
||||||
|
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||||
|
import net.minestom.server.utils.validate.Check;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import static net.minestom.server.instance.Chunk.CHUNK_SECTION_COUNT;
|
||||||
|
import static net.minestom.server.instance.Chunk.CHUNK_SECTION_SIZE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to efficiently store blocks with an optional palette.
|
||||||
|
* <p>
|
||||||
|
* The format used is the one described in the {@link net.minestom.server.network.packet.server.play.ChunkDataPacket},
|
||||||
|
* the reason is that it allows us to write the packet much faster.
|
||||||
|
*/
|
||||||
|
public class PaletteStorage {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum bits per entry value.
|
||||||
|
*/
|
||||||
|
private final static int MAXIMUM_BITS_PER_ENTRY = 15;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The minimum bits per entry value.
|
||||||
|
*/
|
||||||
|
private final static int MINIMUM_BITS_PER_ENTRY = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum bits per entry value which allow for a data palette.
|
||||||
|
*/
|
||||||
|
private final static int PALETTE_MAXIMUM_BITS = 8;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of blocks that should be in one chunk section.
|
||||||
|
*/
|
||||||
|
private final static int BLOCK_COUNT = CHUNK_SECTION_SIZE * CHUNK_SECTION_SIZE * CHUNK_SECTION_SIZE;
|
||||||
|
|
||||||
|
private int bitsPerEntry;
|
||||||
|
private final int bitsIncrement;
|
||||||
|
|
||||||
|
private int valuesPerLong;
|
||||||
|
private boolean hasPalette;
|
||||||
|
|
||||||
|
private long[][] sectionBlocks = new long[CHUNK_SECTION_COUNT][0];
|
||||||
|
|
||||||
|
// chunk section - palette index = block id
|
||||||
|
private Short2ShortLinkedOpenHashMap[] paletteBlockMaps = new Short2ShortLinkedOpenHashMap[CHUNK_SECTION_COUNT];
|
||||||
|
// chunk section - block id = palette index
|
||||||
|
private Short2ShortOpenHashMap[] blockPaletteMaps = new Short2ShortOpenHashMap[CHUNK_SECTION_COUNT];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new palette storage.
|
||||||
|
*
|
||||||
|
* @param bitsPerEntry the number of bits used for one entry (block)
|
||||||
|
* @param bitsIncrement the number of bits to add per-block once the palette array is filled
|
||||||
|
*/
|
||||||
|
public PaletteStorage(int bitsPerEntry, int bitsIncrement) {
|
||||||
|
Check.argCondition(bitsPerEntry > MAXIMUM_BITS_PER_ENTRY, "The maximum bits per entry is 15");
|
||||||
|
// Change the bitsPerEntry to be valid
|
||||||
|
if (bitsPerEntry < MINIMUM_BITS_PER_ENTRY) {
|
||||||
|
bitsPerEntry = MINIMUM_BITS_PER_ENTRY;
|
||||||
|
} else if (MathUtils.isBetween(bitsPerEntry, 9, 14)) {
|
||||||
|
bitsPerEntry = MAXIMUM_BITS_PER_ENTRY;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bitsPerEntry = bitsPerEntry;
|
||||||
|
this.bitsIncrement = bitsIncrement;
|
||||||
|
|
||||||
|
this.valuesPerLong = Long.SIZE / bitsPerEntry;
|
||||||
|
this.hasPalette = bitsPerEntry <= PALETTE_MAXIMUM_BITS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBlockAt(int x, int y, int z, short blockId) {
|
||||||
|
PaletteStorage.setBlockAt(this, x, y, z, blockId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public short getBlockAt(int x, int y, int z) {
|
||||||
|
return PaletteStorage.getBlockAt(this, x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the number of bits that the palette currently take per block.
|
||||||
|
*
|
||||||
|
* @return the bits per entry
|
||||||
|
*/
|
||||||
|
public int getBitsPerEntry() {
|
||||||
|
return bitsPerEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the palette with the index and the block id as the value.
|
||||||
|
*
|
||||||
|
* @param section the chunk section to get the palette from
|
||||||
|
* @return the palette
|
||||||
|
*/
|
||||||
|
public short[] getPalette(int section) {
|
||||||
|
Short2ShortLinkedOpenHashMap paletteBlockMap = paletteBlockMaps[section];
|
||||||
|
return paletteBlockMap != null ? paletteBlockMap.values().toShortArray() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the sections of this object,
|
||||||
|
* the first array representing the chunk section and the second the block position from {@link #getSectionIndex(int, int, int)}.
|
||||||
|
*
|
||||||
|
* @return the section blocks
|
||||||
|
*/
|
||||||
|
public long[][] getSectionBlocks() {
|
||||||
|
return sectionBlocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loops through all the sections and blocks to find unused array (empty chunk section)
|
||||||
|
* <p>
|
||||||
|
* Useful after clearing one or multiple sections of a chunk. Can be unnecessarily expensive if the chunk
|
||||||
|
* is composed of almost-empty sections since the loop will not stop until a non-air block is discovered.
|
||||||
|
*/
|
||||||
|
public synchronized void clean() {
|
||||||
|
for (int i = 0; i < sectionBlocks.length; i++) {
|
||||||
|
long[] section = sectionBlocks[i];
|
||||||
|
|
||||||
|
if (section.length != 0) {
|
||||||
|
boolean canClear = true;
|
||||||
|
for (long blockGroup : section) {
|
||||||
|
if (blockGroup != 0) {
|
||||||
|
canClear = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (canClear) {
|
||||||
|
sectionBlocks[i] = new long[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PaletteStorage copy() {
|
||||||
|
PaletteStorage paletteStorage = new PaletteStorage(bitsPerEntry, bitsIncrement);
|
||||||
|
paletteStorage.sectionBlocks = sectionBlocks.clone();
|
||||||
|
|
||||||
|
paletteStorage.paletteBlockMaps = paletteBlockMaps.clone();
|
||||||
|
paletteStorage.blockPaletteMaps = blockPaletteMaps.clone();
|
||||||
|
|
||||||
|
return paletteStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the palette index for the specified block id.
|
||||||
|
* <p>
|
||||||
|
* Also responsible for resizing the palette when full.
|
||||||
|
*
|
||||||
|
* @param section the chunk section
|
||||||
|
* @param blockId the block id to convert
|
||||||
|
* @return the palette index of {@code blockId}
|
||||||
|
*/
|
||||||
|
private short getPaletteIndex(int section, short blockId) {
|
||||||
|
if (!hasPalette) {
|
||||||
|
return blockId;
|
||||||
|
}
|
||||||
|
|
||||||
|
Short2ShortOpenHashMap blockPaletteMap = blockPaletteMaps[section];
|
||||||
|
if (blockPaletteMap == null) {
|
||||||
|
blockPaletteMap = createBlockPaletteMap();
|
||||||
|
blockPaletteMaps[section] = blockPaletteMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!blockPaletteMap.containsKey(blockId)) {
|
||||||
|
Short2ShortLinkedOpenHashMap paletteBlockMap = paletteBlockMaps[section];
|
||||||
|
if (paletteBlockMap == null) {
|
||||||
|
paletteBlockMap = createPaletteBlockMap();
|
||||||
|
paletteBlockMaps[section] = paletteBlockMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize the palette if full
|
||||||
|
if (paletteBlockMap.size() >= getMaxPaletteSize()) {
|
||||||
|
resize(bitsPerEntry + bitsIncrement);
|
||||||
|
}
|
||||||
|
|
||||||
|
final short paletteIndex = (short) (paletteBlockMap.lastShortKey() + 1);
|
||||||
|
paletteBlockMap.put(paletteIndex, blockId);
|
||||||
|
blockPaletteMap.put(blockId, paletteIndex);
|
||||||
|
return paletteIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
return blockPaletteMap.get(blockId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resizes the array.
|
||||||
|
* <p>
|
||||||
|
* Will create a new palette storage to set all the current blocks, and the data will be transferred to 'this'.
|
||||||
|
*
|
||||||
|
* @param newBitsPerEntry the new bits per entry count
|
||||||
|
*/
|
||||||
|
private synchronized void resize(int newBitsPerEntry) {
|
||||||
|
PaletteStorage paletteStorageCache = new PaletteStorage(newBitsPerEntry, bitsIncrement);
|
||||||
|
paletteStorageCache.paletteBlockMaps = paletteBlockMaps;
|
||||||
|
paletteStorageCache.blockPaletteMaps = blockPaletteMaps;
|
||||||
|
|
||||||
|
for (int y = 0; y < Chunk.CHUNK_SIZE_Y; y++) {
|
||||||
|
for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) {
|
||||||
|
for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z++) {
|
||||||
|
final short blockId = getBlockAt(x, y, z);
|
||||||
|
paletteStorageCache.setBlockAt(x, y, z, blockId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bitsPerEntry = newBitsPerEntry;
|
||||||
|
|
||||||
|
this.valuesPerLong = paletteStorageCache.valuesPerLong;
|
||||||
|
this.hasPalette = paletteStorageCache.hasPalette;
|
||||||
|
|
||||||
|
this.sectionBlocks = paletteStorageCache.sectionBlocks;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the maximum number of blocks that the current palette (could be the global one) can take.
|
||||||
|
*
|
||||||
|
* @return the number of blocks possible in the palette
|
||||||
|
*/
|
||||||
|
private int getMaxPaletteSize() {
|
||||||
|
return 1 << bitsPerEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Magic values generated with "Integer.MAX_VALUE >> (31 - bitsPerIndex)" for bitsPerIndex between 4 and 15
|
||||||
|
private static final int[] MAGIC_MASKS =
|
||||||
|
{0, 0, 0, 0,
|
||||||
|
15, 31, 63, 127, 255,
|
||||||
|
511, 1023, 2047, 4095,
|
||||||
|
8191, 16383, 32767};
|
||||||
|
|
||||||
|
private static void setBlockAt(@NotNull PaletteStorage paletteStorage, int x, int y, int z, short blockId) {
|
||||||
|
if (y < 0 || y >= Chunk.CHUNK_SIZE_Y) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int section = ChunkUtils.getSectionAt(y);
|
||||||
|
|
||||||
|
final int valuesPerLong = paletteStorage.valuesPerLong;
|
||||||
|
final int bitsPerEntry = paletteStorage.bitsPerEntry;
|
||||||
|
|
||||||
|
if (paletteStorage.sectionBlocks[section].length == 0) {
|
||||||
|
if (blockId == 0) {
|
||||||
|
// Section is empty and method is trying to place an air block, stop unnecessary computation
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the section
|
||||||
|
paletteStorage.sectionBlocks[section] = new long[getSize(valuesPerLong)];
|
||||||
|
}
|
||||||
|
|
||||||
|
x = toChunkCoordinate(x);
|
||||||
|
z = toChunkCoordinate(z);
|
||||||
|
|
||||||
|
// Change to palette value
|
||||||
|
blockId = paletteStorage.getPaletteIndex(section, blockId);
|
||||||
|
|
||||||
|
final int sectionIndex = getSectionIndex(x, y, z);
|
||||||
|
|
||||||
|
final int index = sectionIndex / valuesPerLong;
|
||||||
|
final int bitIndex = (sectionIndex % valuesPerLong) * bitsPerEntry;
|
||||||
|
|
||||||
|
final long[] sectionBlock = paletteStorage.sectionBlocks[section];
|
||||||
|
|
||||||
|
long block = sectionBlock[index];
|
||||||
|
{
|
||||||
|
final long clear = MAGIC_MASKS[bitsPerEntry];
|
||||||
|
|
||||||
|
block |= clear << bitIndex;
|
||||||
|
block ^= clear << bitIndex;
|
||||||
|
block |= (long) blockId << bitIndex;
|
||||||
|
|
||||||
|
sectionBlock[index] = block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static short getBlockAt(@NotNull PaletteStorage paletteStorage, int x, int y, int z) {
|
||||||
|
if (y < 0 || y >= Chunk.CHUNK_SIZE_Y) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int section = ChunkUtils.getSectionAt(y);
|
||||||
|
final long[] blocks;
|
||||||
|
|
||||||
|
// Retrieve the longs and check if the section is empty
|
||||||
|
{
|
||||||
|
blocks = paletteStorage.sectionBlocks[section];
|
||||||
|
|
||||||
|
if (blocks.length == 0) {
|
||||||
|
// Section is not loaded, can only be air
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
x = toChunkCoordinate(x);
|
||||||
|
z = toChunkCoordinate(z);
|
||||||
|
|
||||||
|
final int sectionIndex = getSectionIndex(x, y, z);
|
||||||
|
|
||||||
|
final int valuesPerLong = paletteStorage.valuesPerLong;
|
||||||
|
final int bitsPerEntry = paletteStorage.bitsPerEntry;
|
||||||
|
|
||||||
|
final int index = sectionIndex / valuesPerLong;
|
||||||
|
final int bitIndex = sectionIndex % valuesPerLong * bitsPerEntry;
|
||||||
|
|
||||||
|
final long value = blocks[index] >> bitIndex & MAGIC_MASKS[bitsPerEntry];
|
||||||
|
|
||||||
|
// Change to palette value and return
|
||||||
|
return paletteStorage.hasPalette ?
|
||||||
|
paletteStorage.paletteBlockMaps[section].get((short) value) :
|
||||||
|
(short) value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Short2ShortLinkedOpenHashMap createPaletteBlockMap() {
|
||||||
|
Short2ShortLinkedOpenHashMap map = new Short2ShortLinkedOpenHashMap(CHUNK_SECTION_SIZE);
|
||||||
|
map.put((short) 0, (short) 0);
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Short2ShortOpenHashMap createBlockPaletteMap() {
|
||||||
|
Short2ShortOpenHashMap map = new Short2ShortOpenHashMap(CHUNK_SECTION_SIZE);
|
||||||
|
map.put((short) 0, (short) 0);
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the array length of one section based on the number of values which can be stored in one long.
|
||||||
|
*
|
||||||
|
* @param valuesPerLong the number of values per long
|
||||||
|
* @return the array length based on {@code valuesPerLong}
|
||||||
|
*/
|
||||||
|
private static int getSize(int valuesPerLong) {
|
||||||
|
return (BLOCK_COUNT + valuesPerLong - 1) / valuesPerLong;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a world coordinate to a chunk one.
|
||||||
|
*
|
||||||
|
* @param xz the world coordinate
|
||||||
|
* @return the chunk coordinate of {@code xz}
|
||||||
|
*/
|
||||||
|
private static int toChunkCoordinate(int xz) {
|
||||||
|
xz %= 16;
|
||||||
|
if (xz < 0) {
|
||||||
|
xz += CHUNK_SECTION_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return xz;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the index of the block on the section array based on the block position.
|
||||||
|
*
|
||||||
|
* @param x the chunk X
|
||||||
|
* @param y the chunk Y
|
||||||
|
* @param z the chunk Z
|
||||||
|
* @return the section index of the position
|
||||||
|
*/
|
||||||
|
public static int getSectionIndex(int x, int y, int z) {
|
||||||
|
y %= CHUNK_SECTION_SIZE;
|
||||||
|
return y << 8 | z << 4 | x;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -186,7 +186,6 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View
|
|||||||
setItemStackInternal(i, ItemStack.getAirItem());
|
setItemStackInternal(i, ItemStack.getAirItem());
|
||||||
}
|
}
|
||||||
// Send the cleared inventory to viewers
|
// Send the cleared inventory to viewers
|
||||||
// TODO cached packet with empty content
|
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,6 @@ import net.minestom.server.inventory.click.InventoryClickResult;
|
|||||||
import net.minestom.server.inventory.condition.InventoryCondition;
|
import net.minestom.server.inventory.condition.InventoryCondition;
|
||||||
import net.minestom.server.item.ItemStack;
|
import net.minestom.server.item.ItemStack;
|
||||||
import net.minestom.server.item.StackingRule;
|
import net.minestom.server.item.StackingRule;
|
||||||
import net.minestom.server.network.PacketWriterUtils;
|
|
||||||
import net.minestom.server.network.packet.server.play.EntityEquipmentPacket;
|
import net.minestom.server.network.packet.server.play.EntityEquipmentPacket;
|
||||||
import net.minestom.server.network.packet.server.play.SetSlotPacket;
|
import net.minestom.server.network.packet.server.play.SetSlotPacket;
|
||||||
import net.minestom.server.network.packet.server.play.WindowItemsPacket;
|
import net.minestom.server.network.packet.server.play.WindowItemsPacket;
|
||||||
@ -214,7 +213,7 @@ public class PlayerInventory implements InventoryModifier, InventoryClickHandler
|
|||||||
* the inventory items
|
* the inventory items
|
||||||
*/
|
*/
|
||||||
public void update() {
|
public void update() {
|
||||||
PacketWriterUtils.writeAndSend(player, createWindowItemsPacket());
|
player.getPlayerConnection().sendPacket(createWindowItemsPacket());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -336,7 +335,7 @@ public class PlayerInventory implements InventoryModifier, InventoryClickHandler
|
|||||||
/**
|
/**
|
||||||
* Refreshes an inventory slot.
|
* Refreshes an inventory slot.
|
||||||
*
|
*
|
||||||
* @param slot the packet slot
|
* @param slot the packet slot,
|
||||||
* see {@link net.minestom.server.utils.inventory.PlayerInventoryUtils#convertToPacketSlot(int)}
|
* see {@link net.minestom.server.utils.inventory.PlayerInventoryUtils#convertToPacketSlot(int)}
|
||||||
* @param itemStack the item stack in the slot
|
* @param itemStack the item stack in the slot
|
||||||
*/
|
*/
|
||||||
|
@ -37,7 +37,7 @@ import java.util.*;
|
|||||||
*/
|
*/
|
||||||
public class ItemStack implements DataContainer {
|
public class ItemStack implements DataContainer {
|
||||||
|
|
||||||
private static final StackingRule DEFAULT_STACKING_RULE = new VanillaStackingRule(64);
|
private static final StackingRule VANILLA_STACKING_RULE = new VanillaStackingRule(64);
|
||||||
|
|
||||||
private Material material;
|
private Material material;
|
||||||
|
|
||||||
@ -76,7 +76,7 @@ public class ItemStack implements DataContainer {
|
|||||||
|
|
||||||
{
|
{
|
||||||
if (defaultStackingRule == null)
|
if (defaultStackingRule == null)
|
||||||
defaultStackingRule = DEFAULT_STACKING_RULE;
|
defaultStackingRule = VANILLA_STACKING_RULE;
|
||||||
this.stackingRule = defaultStackingRule;
|
this.stackingRule = defaultStackingRule;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -767,4 +767,4 @@ public class ItemStack implements DataContainer {
|
|||||||
public void onInventoryClick(@NotNull Player player, @NotNull ClickType clickType, int slot, boolean playerInventory) {
|
public void onInventoryClick(@NotNull Player player, @NotNull ClickType clickType, int slot, boolean playerInventory) {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,6 +2,7 @@ package net.minestom.server.item.rule;
|
|||||||
|
|
||||||
import net.minestom.server.item.ItemStack;
|
import net.minestom.server.item.ItemStack;
|
||||||
import net.minestom.server.item.StackingRule;
|
import net.minestom.server.item.StackingRule;
|
||||||
|
import net.minestom.server.utils.MathUtils;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public class VanillaStackingRule extends StackingRule {
|
public class VanillaStackingRule extends StackingRule {
|
||||||
@ -17,7 +18,7 @@ public class VanillaStackingRule extends StackingRule {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canApply(@NotNull ItemStack item, int newAmount) {
|
public boolean canApply(@NotNull ItemStack item, int newAmount) {
|
||||||
return newAmount > 0 && newAmount <= getMaxSize();
|
return MathUtils.isBetween(newAmount, 0, getMaxSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
|
@ -9,9 +9,9 @@ import net.minestom.server.command.CommandManager;
|
|||||||
import net.minestom.server.entity.Player;
|
import net.minestom.server.entity.Player;
|
||||||
import net.minestom.server.event.player.PlayerChatEvent;
|
import net.minestom.server.event.player.PlayerChatEvent;
|
||||||
import net.minestom.server.network.ConnectionManager;
|
import net.minestom.server.network.ConnectionManager;
|
||||||
import net.minestom.server.network.PacketWriterUtils;
|
|
||||||
import net.minestom.server.network.packet.client.play.ClientChatMessagePacket;
|
import net.minestom.server.network.packet.client.play.ClientChatMessagePacket;
|
||||||
import net.minestom.server.network.packet.server.play.ChatMessagePacket;
|
import net.minestom.server.network.packet.server.play.ChatMessagePacket;
|
||||||
|
import net.minestom.server.utils.PacketUtils;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
@ -62,7 +62,7 @@ public class ChatMessageListener {
|
|||||||
ChatMessagePacket chatMessagePacket =
|
ChatMessagePacket chatMessagePacket =
|
||||||
new ChatMessagePacket(jsonMessage, ChatMessagePacket.Position.CHAT, player.getUuid());
|
new ChatMessagePacket(jsonMessage, ChatMessagePacket.Position.CHAT, player.getUuid());
|
||||||
|
|
||||||
PacketWriterUtils.writeAndSend(recipients, chatMessagePacket);
|
PacketUtils.sendGroupedPacket(recipients, chatMessagePacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -13,8 +13,9 @@ public class CreativeInventoryActionListener {
|
|||||||
if (player.getGameMode() != GameMode.CREATIVE)
|
if (player.getGameMode() != GameMode.CREATIVE)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
final ItemStack item = packet.item;
|
|
||||||
short slot = packet.slot;
|
short slot = packet.slot;
|
||||||
|
final ItemStack item = packet.item;
|
||||||
|
|
||||||
if (slot != -1) {
|
if (slot != -1) {
|
||||||
// Set item
|
// Set item
|
||||||
slot = (short) PlayerInventoryUtils.convertSlot(slot, PlayerInventoryUtils.OFFSET);
|
slot = (short) PlayerInventoryUtils.convertSlot(slot, PlayerInventoryUtils.OFFSET);
|
||||||
|
@ -8,6 +8,7 @@ import net.minestom.server.network.packet.client.play.ClientPlayerPositionPacket
|
|||||||
import net.minestom.server.network.packet.client.play.ClientPlayerRotationPacket;
|
import net.minestom.server.network.packet.client.play.ClientPlayerRotationPacket;
|
||||||
import net.minestom.server.utils.Position;
|
import net.minestom.server.utils.Position;
|
||||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public class PlayerPositionListener {
|
public class PlayerPositionListener {
|
||||||
|
|
||||||
@ -47,7 +48,7 @@ public class PlayerPositionListener {
|
|||||||
processMovement(player, x, y, z, yaw, pitch, onGround);
|
processMovement(player, x, y, z, yaw, pitch, onGround);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void processMovement(Player player, float x, float y, float z,
|
private static void processMovement(@NotNull Player player, float x, float y, float z,
|
||||||
float yaw, float pitch, boolean onGround) {
|
float yaw, float pitch, boolean onGround) {
|
||||||
|
|
||||||
// Try to move in an unloaded chunk, prevent it
|
// Try to move in an unloaded chunk, prevent it
|
||||||
|
@ -1,21 +1,24 @@
|
|||||||
package net.minestom.server.listener.manager;
|
package net.minestom.server.listener.manager;
|
||||||
|
|
||||||
import net.minestom.server.entity.Player;
|
import net.minestom.server.entity.Player;
|
||||||
import net.minestom.server.network.packet.client.ClientPlayPacket;
|
import net.minestom.server.network.ConnectionManager;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface used to add a listener for incoming packets with {@link net.minestom.server.network.ConnectionManager#onPacketReceive(PacketConsumer)}.
|
* Interface used to add a listener for incoming/outgoing packets with
|
||||||
|
* {@link ConnectionManager#onPacketReceive(PacketConsumer)} and {@link ConnectionManager#onPacketSend(PacketConsumer)}.
|
||||||
|
*
|
||||||
|
* @param <T> the packet type
|
||||||
*/
|
*/
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface PacketConsumer {
|
public interface PacketConsumer<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a packet is received from the client.
|
* Called when a packet is received/sent from/to a client.
|
||||||
*
|
*
|
||||||
* @param player the player who sent the packet
|
* @param player the player concerned by the packet
|
||||||
* @param packetController the packet controller, used to cancel or control which listener will be called
|
* @param packetController the packet controller, can be used to cancel the packet
|
||||||
* @param packet the received packet
|
* @param packet the packet
|
||||||
*/
|
*/
|
||||||
void accept(@NotNull Player player, @NotNull PacketController packetController, @NotNull ClientPlayPacket packet);
|
void accept(@NotNull Player player, @NotNull PacketController packetController, @NotNull T packet);
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,15 @@
|
|||||||
package net.minestom.server.listener.manager;
|
package net.minestom.server.listener.manager;
|
||||||
|
|
||||||
import net.minestom.server.entity.Player;
|
import net.minestom.server.entity.Player;
|
||||||
import net.minestom.server.network.packet.client.ClientPlayPacket;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to control the output of a packet in {@link PacketConsumer#accept(Player, PacketController, ClientPlayPacket)}.
|
* Used to control the output of a packet in {@link PacketConsumer#accept(Player, PacketController, Object)}.
|
||||||
*/
|
*/
|
||||||
public class PacketController {
|
public class PacketController {
|
||||||
|
|
||||||
private boolean cancel;
|
private boolean cancel;
|
||||||
private PacketListenerConsumer packetListenerConsumer;
|
|
||||||
|
|
||||||
protected PacketController(@Nullable PacketListenerConsumer packetListenerConsumer) {
|
protected PacketController() {
|
||||||
this.packetListenerConsumer = packetListenerConsumer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -33,25 +29,4 @@ public class PacketController {
|
|||||||
public void setCancel(boolean cancel) {
|
public void setCancel(boolean cancel) {
|
||||||
this.cancel = cancel;
|
this.cancel = cancel;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the listener associated with the packet.
|
|
||||||
*
|
|
||||||
* @return the packet's listener, null if not present
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public PacketListenerConsumer getPacketListenerConsumer() {
|
|
||||||
return packetListenerConsumer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Changes the packet listener, setting it to null cancel the listener.
|
|
||||||
* <p>
|
|
||||||
* WARNING: this will overwrite the default minestom listener, be sure to know what you are doing.
|
|
||||||
*
|
|
||||||
* @param packetListenerConsumer the new packet listener, can be null
|
|
||||||
*/
|
|
||||||
public void setPacketListenerConsumer(@Nullable PacketListenerConsumer packetListenerConsumer) {
|
|
||||||
this.packetListenerConsumer = packetListenerConsumer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -6,13 +6,17 @@ import net.minestom.server.listener.*;
|
|||||||
import net.minestom.server.network.ConnectionManager;
|
import net.minestom.server.network.ConnectionManager;
|
||||||
import net.minestom.server.network.packet.client.ClientPlayPacket;
|
import net.minestom.server.network.packet.client.ClientPlayPacket;
|
||||||
import net.minestom.server.network.packet.client.play.*;
|
import net.minestom.server.network.packet.client.play.*;
|
||||||
|
import net.minestom.server.network.packet.server.ServerPacket;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
public final class PacketListenerManager {
|
public final class PacketListenerManager {
|
||||||
|
|
||||||
|
public final static Logger LOGGER = LoggerFactory.getLogger(PacketListenerManager.class);
|
||||||
private static final ConnectionManager CONNECTION_MANAGER = MinecraftServer.getConnectionManager();
|
private static final ConnectionManager CONNECTION_MANAGER = MinecraftServer.getConnectionManager();
|
||||||
|
|
||||||
private final Map<Class<? extends ClientPlayPacket>, PacketListenerConsumer> listeners = new ConcurrentHashMap<>();
|
private final Map<Class<? extends ClientPlayPacket>, PacketListenerConsumer> listeners = new ConcurrentHashMap<>();
|
||||||
@ -56,7 +60,7 @@ public final class PacketListenerManager {
|
|||||||
* @param player the player who sent the packet
|
* @param player the player who sent the packet
|
||||||
* @param <T> the packet type
|
* @param <T> the packet type
|
||||||
*/
|
*/
|
||||||
public <T extends ClientPlayPacket> void process(@NotNull T packet, @NotNull Player player) {
|
public <T extends ClientPlayPacket> void processClientPacket(@NotNull T packet, @NotNull Player player) {
|
||||||
|
|
||||||
final Class clazz = packet.getClass();
|
final Class clazz = packet.getClass();
|
||||||
|
|
||||||
@ -64,26 +68,37 @@ public final class PacketListenerManager {
|
|||||||
|
|
||||||
// Listener can be null if none has been set before, call PacketConsumer anyway
|
// Listener can be null if none has been set before, call PacketConsumer anyway
|
||||||
if (packetListenerConsumer == null) {
|
if (packetListenerConsumer == null) {
|
||||||
System.err.println("Packet " + clazz + " does not have any default listener! (The issue comes from Minestom)");
|
LOGGER.error("Packet " + clazz + " does not have any default listener! (The issue comes from Minestom)");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final PacketController packetController = new PacketController();
|
||||||
final PacketController packetController = new PacketController(packetListenerConsumer);
|
for (PacketConsumer<ClientPlayPacket> packetConsumer : CONNECTION_MANAGER.getReceivePacketConsumers()) {
|
||||||
for (PacketConsumer packetConsumer : CONNECTION_MANAGER.getReceivePacketConsumers()) {
|
|
||||||
packetConsumer.accept(player, packetController, packet);
|
packetConsumer.accept(player, packetController, packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packetController.isCancel())
|
if (packetController.isCancel())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Get the new listener (or the same) from the packet controller
|
// Finally execute the listener
|
||||||
packetListenerConsumer = packetController.getPacketListenerConsumer();
|
packetListenerConsumer.accept(packet, player);
|
||||||
|
}
|
||||||
|
|
||||||
// Call the listener if not null
|
/**
|
||||||
// (can be null because no listener is set, or because it has been changed by the controller)
|
* Executes the consumers from {@link ConnectionManager#onPacketSend(PacketConsumer)}.
|
||||||
if (packetListenerConsumer != null) {
|
*
|
||||||
packetListenerConsumer.accept(packet, player);
|
* @param packet the packet to process
|
||||||
|
* @param player the player which should receive the packet
|
||||||
|
* @param <T> the packet type
|
||||||
|
* @return true if the packet is not cancelled, false otherwise
|
||||||
|
*/
|
||||||
|
public <T extends ServerPacket> boolean processServerPacket(@NotNull T packet, @NotNull Player player) {
|
||||||
|
final PacketController packetController = new PacketController();
|
||||||
|
for (PacketConsumer<ServerPacket> packetConsumer : CONNECTION_MANAGER.getSendPacketConsumers()) {
|
||||||
|
packetConsumer.accept(player, packetController, packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return !packetController.isCancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -4,10 +4,13 @@ import net.minestom.server.chat.JsonMessage;
|
|||||||
import net.minestom.server.entity.Player;
|
import net.minestom.server.entity.Player;
|
||||||
import net.minestom.server.entity.fakeplayer.FakePlayer;
|
import net.minestom.server.entity.fakeplayer.FakePlayer;
|
||||||
import net.minestom.server.listener.manager.PacketConsumer;
|
import net.minestom.server.listener.manager.PacketConsumer;
|
||||||
|
import net.minestom.server.network.packet.client.ClientPlayPacket;
|
||||||
import net.minestom.server.network.packet.client.login.LoginStartPacket;
|
import net.minestom.server.network.packet.client.login.LoginStartPacket;
|
||||||
|
import net.minestom.server.network.packet.server.ServerPacket;
|
||||||
import net.minestom.server.network.packet.server.login.LoginSuccessPacket;
|
import net.minestom.server.network.packet.server.login.LoginSuccessPacket;
|
||||||
import net.minestom.server.network.packet.server.play.ChatMessagePacket;
|
import net.minestom.server.network.packet.server.play.ChatMessagePacket;
|
||||||
import net.minestom.server.network.player.PlayerConnection;
|
import net.minestom.server.network.player.PlayerConnection;
|
||||||
|
import net.minestom.server.utils.PacketUtils;
|
||||||
import net.minestom.server.utils.callback.validator.PlayerValidator;
|
import net.minestom.server.utils.callback.validator.PlayerValidator;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
@ -26,7 +29,9 @@ public final class ConnectionManager {
|
|||||||
private final Map<PlayerConnection, Player> connectionPlayerMap = Collections.synchronizedMap(new HashMap<>());
|
private final Map<PlayerConnection, Player> connectionPlayerMap = Collections.synchronizedMap(new HashMap<>());
|
||||||
|
|
||||||
// All the consumers to call once a packet is received
|
// All the consumers to call once a packet is received
|
||||||
private final List<PacketConsumer> receivePacketConsumers = new CopyOnWriteArrayList<>();
|
private final List<PacketConsumer<ClientPlayPacket>> receivePacketConsumers = new CopyOnWriteArrayList<>();
|
||||||
|
// All the consumers to call once a packet is sent
|
||||||
|
private final List<PacketConsumer<ServerPacket>> sendPacketConsumers = new CopyOnWriteArrayList<>();
|
||||||
// The uuid provider once a player login
|
// The uuid provider once a player login
|
||||||
private UuidProvider uuidProvider;
|
private UuidProvider uuidProvider;
|
||||||
// The player provider to have your own Player implementation
|
// The player provider to have your own Player implementation
|
||||||
@ -115,7 +120,8 @@ public final class ConnectionManager {
|
|||||||
private void broadcastJson(@NotNull String json, @NotNull Collection<Player> recipients) {
|
private void broadcastJson(@NotNull String json, @NotNull Collection<Player> recipients) {
|
||||||
ChatMessagePacket chatMessagePacket =
|
ChatMessagePacket chatMessagePacket =
|
||||||
new ChatMessagePacket(json, ChatMessagePacket.Position.SYSTEM_MESSAGE);
|
new ChatMessagePacket(json, ChatMessagePacket.Position.SYSTEM_MESSAGE);
|
||||||
PacketWriterUtils.writeAndSend(recipients, chatMessagePacket);
|
|
||||||
|
PacketUtils.sendGroupedPacket(recipients, chatMessagePacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Collection<Player> getRecipients(@Nullable PlayerValidator condition) {
|
private Collection<Player> getRecipients(@Nullable PlayerValidator condition) {
|
||||||
@ -142,7 +148,7 @@ public final class ConnectionManager {
|
|||||||
* @return an unmodifiable list of packet's consumers
|
* @return an unmodifiable list of packet's consumers
|
||||||
*/
|
*/
|
||||||
@NotNull
|
@NotNull
|
||||||
public List<PacketConsumer> getReceivePacketConsumers() {
|
public List<PacketConsumer<ClientPlayPacket>> getReceivePacketConsumers() {
|
||||||
return Collections.unmodifiableList(receivePacketConsumers);
|
return Collections.unmodifiableList(receivePacketConsumers);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,14 +157,39 @@ public final class ConnectionManager {
|
|||||||
*
|
*
|
||||||
* @param packetConsumer the packet consumer
|
* @param packetConsumer the packet consumer
|
||||||
*/
|
*/
|
||||||
public void onPacketReceive(@NotNull PacketConsumer packetConsumer) {
|
public void onPacketReceive(@NotNull PacketConsumer<ClientPlayPacket> packetConsumer) {
|
||||||
this.receivePacketConsumers.add(packetConsumer);
|
this.receivePacketConsumers.add(packetConsumer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all the listeners which are called for each packet sent.
|
||||||
|
*
|
||||||
|
* @return an unmodifiable list of packet's consumers
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public List<PacketConsumer<ServerPacket>> getSendPacketConsumers() {
|
||||||
|
return Collections.unmodifiableList(sendPacketConsumers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a consumer to call once a packet is sent.
|
||||||
|
* <p>
|
||||||
|
* Be aware that it is possible for the same packet instance to be used multiple time,
|
||||||
|
* changing the object fields could lead to issues.
|
||||||
|
* (consider canceling the packet instead and send your own)
|
||||||
|
*
|
||||||
|
* @param packetConsumer the packet consumer
|
||||||
|
*/
|
||||||
|
public void onPacketSend(@NotNull PacketConsumer<ServerPacket> packetConsumer) {
|
||||||
|
this.sendPacketConsumers.add(packetConsumer);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes how {@link UUID} are attributed to players.
|
* Changes how {@link UUID} are attributed to players.
|
||||||
* <p>
|
* <p>
|
||||||
* Shouldn't be override if already defined.
|
* Shouldn't be override if already defined.
|
||||||
|
* <p>
|
||||||
|
* Be aware that it is possible for an UUID provider to be ignored, for example in the case of a proxy (eg: velocity).
|
||||||
*
|
*
|
||||||
* @param uuidProvider the new player connection uuid provider,
|
* @param uuidProvider the new player connection uuid provider,
|
||||||
* setting it to null would apply a random UUID for each player connection
|
* setting it to null would apply a random UUID for each player connection
|
||||||
@ -248,10 +279,13 @@ public final class ConnectionManager {
|
|||||||
* @param uuid the new player uuid
|
* @param uuid the new player uuid
|
||||||
* @param username the new player username
|
* @param username the new player username
|
||||||
* @param connection the new player connection
|
* @param connection the new player connection
|
||||||
|
* @return the newly created player object
|
||||||
*/
|
*/
|
||||||
public void createPlayer(@NotNull UUID uuid, @NotNull String username, @NotNull PlayerConnection connection) {
|
@NotNull
|
||||||
|
public Player createPlayer(@NotNull UUID uuid, @NotNull String username, @NotNull PlayerConnection connection) {
|
||||||
final Player player = getPlayerProvider().createPlayer(uuid, username, connection);
|
final Player player = getPlayerProvider().createPlayer(uuid, username, connection);
|
||||||
createPlayer(player);
|
createPlayer(player);
|
||||||
|
return player;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -277,12 +311,14 @@ public final class ConnectionManager {
|
|||||||
* @param connection the player connection
|
* @param connection the player connection
|
||||||
* @param uuid the uuid of the player
|
* @param uuid the uuid of the player
|
||||||
* @param username the username of the player
|
* @param username the username of the player
|
||||||
|
* @return the newly created player object
|
||||||
*/
|
*/
|
||||||
public void startPlayState(@NotNull PlayerConnection connection, @NotNull UUID uuid, @NotNull String username) {
|
@NotNull
|
||||||
|
public Player startPlayState(@NotNull PlayerConnection connection, @NotNull UUID uuid, @NotNull String username) {
|
||||||
LoginSuccessPacket loginSuccessPacket = new LoginSuccessPacket(uuid, username);
|
LoginSuccessPacket loginSuccessPacket = new LoginSuccessPacket(uuid, username);
|
||||||
connection.sendPacket(loginSuccessPacket);
|
connection.sendPacket(loginSuccessPacket);
|
||||||
|
|
||||||
connection.setConnectionState(ConnectionState.PLAY);
|
connection.setConnectionState(ConnectionState.PLAY);
|
||||||
createPlayer(uuid, username, connection);
|
return createPlayer(uuid, username, connection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,16 @@ import org.slf4j.LoggerFactory;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Responsible for processing client packets.
|
||||||
|
* <p>
|
||||||
|
* You can retrieve the different packet handlers per state (status/login/play)
|
||||||
|
* from the {@link net.minestom.server.network.packet.client.handler.ClientPacketsHandler} class.
|
||||||
|
* <p>
|
||||||
|
* Packet handlers are cached here and can be retrieved with {@link #getStatusPacketsHandler()}, {@link #getLoginPacketsHandler()}
|
||||||
|
* and {@link #getPlayPacketsHandler()}. The one to use depend on the type of packet you need to retrieve (the packet id 0 does not have
|
||||||
|
* the same meaning as it is a login or play packet).
|
||||||
|
*/
|
||||||
public final class PacketProcessor {
|
public final class PacketProcessor {
|
||||||
|
|
||||||
private final static Logger LOGGER = LoggerFactory.getLogger(PacketProcessor.class);
|
private final static Logger LOGGER = LoggerFactory.getLogger(PacketProcessor.class);
|
||||||
@ -94,10 +104,43 @@ public final class PacketProcessor {
|
|||||||
return connectionPlayerConnectionMap.get(channel);
|
return connectionPlayerConnectionMap.get(channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removePlayerConnection(ChannelHandlerContext channel) {
|
public void removePlayerConnection(@NotNull ChannelHandlerContext channel) {
|
||||||
connectionPlayerConnectionMap.remove(channel);
|
connectionPlayerConnectionMap.remove(channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the handler for client status packets.
|
||||||
|
*
|
||||||
|
* @return the status packets handler
|
||||||
|
* @see <a href="https://wiki.vg/Protocol#Status">Status packets</a>
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public ClientStatusPacketsHandler getStatusPacketsHandler() {
|
||||||
|
return statusPacketsHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the handler for client login packets.
|
||||||
|
*
|
||||||
|
* @return the status login handler
|
||||||
|
* @see <a href="https://wiki.vg/Protocol#Login">Login packets</a>
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public ClientLoginPacketsHandler getLoginPacketsHandler() {
|
||||||
|
return loginPacketsHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the handler for client play packets.
|
||||||
|
*
|
||||||
|
* @return the play packets handler
|
||||||
|
* @see <a href="https://wiki.vg/Protocol#Play">Play packets</a>
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public ClientPlayPacketsHandler getPlayPacketsHandler() {
|
||||||
|
return playPacketsHandler;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls {@link Readable#read(BinaryReader)} and catch all the exceptions to be printed using the packet processor logger.
|
* Calls {@link Readable#read(BinaryReader)} and catch all the exceptions to be printed using the packet processor logger.
|
||||||
*
|
*
|
||||||
@ -106,12 +149,20 @@ public final class PacketProcessor {
|
|||||||
* @param reader the buffer containing the packet
|
* @param reader the buffer containing the packet
|
||||||
*/
|
*/
|
||||||
private void safeRead(@NotNull PlayerConnection connection, @NotNull Readable readable, @NotNull BinaryReader reader) {
|
private void safeRead(@NotNull PlayerConnection connection, @NotNull Readable readable, @NotNull BinaryReader reader) {
|
||||||
|
final int readableBytes = reader.available();
|
||||||
|
|
||||||
|
// Check if there is anything to read
|
||||||
|
if (readableBytes == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
readable.read(reader);
|
readable.read(reader);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
final Player player = connection.getPlayer();
|
final Player player = connection.getPlayer();
|
||||||
final String username = player != null ? player.getUsername() : "null";
|
final String username = player != null ? player.getUsername() : "null";
|
||||||
LOGGER.warn("Connection " + connection.getRemoteAddress() + " (" + username + ") sent an unexpected packet.");
|
LOGGER.warn("Connection " + connection.getRemoteAddress() + " (" + username + ") sent an unexpected packet.");
|
||||||
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,101 +0,0 @@
|
|||||||
package net.minestom.server.network;
|
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
import net.minestom.server.MinecraftServer;
|
|
||||||
import net.minestom.server.entity.Player;
|
|
||||||
import net.minestom.server.network.packet.server.ServerPacket;
|
|
||||||
import net.minestom.server.network.player.PlayerConnection;
|
|
||||||
import net.minestom.server.utils.PacketUtils;
|
|
||||||
import net.minestom.server.utils.player.PlayerUtils;
|
|
||||||
import net.minestom.server.utils.thread.MinestomThread;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utils class used to write {@link ServerPacket} in the appropriate thread pool.
|
|
||||||
* <p>
|
|
||||||
* WARNING: those methods do not guarantee a receive order.
|
|
||||||
*/
|
|
||||||
public final class PacketWriterUtils {
|
|
||||||
|
|
||||||
private static final ExecutorService PACKET_WRITER_POOL = new MinestomThread(MinecraftServer.THREAD_COUNT_PACKET_WRITER, MinecraftServer.THREAD_NAME_PACKET_WRITER);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes the {@link ServerPacket} in the writer thread pool.
|
|
||||||
* <p>
|
|
||||||
* WARNING: should not be used if the packet receive order is important
|
|
||||||
*
|
|
||||||
* @param serverPacket the packet to write
|
|
||||||
* @param consumer the consumer called once the packet has been written
|
|
||||||
*/
|
|
||||||
public static void writeCallbackPacket(@NotNull ServerPacket serverPacket, @NotNull Consumer<ByteBuf> consumer) {
|
|
||||||
PACKET_WRITER_POOL.execute(() -> {
|
|
||||||
final ByteBuf buffer = PacketUtils.writePacket(serverPacket);
|
|
||||||
consumer.accept(buffer);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a {@link ServerPacket} in the writer thread pool and send it to every players in {@code players}.
|
|
||||||
* <p>
|
|
||||||
* WARNING: should not be used if the packet receive order is important
|
|
||||||
*
|
|
||||||
* @param players the players list to send the packet to
|
|
||||||
* @param serverPacket the packet to write and send
|
|
||||||
*/
|
|
||||||
public static void writeAndSend(@NotNull Collection<Player> players, @NotNull ServerPacket serverPacket) {
|
|
||||||
PACKET_WRITER_POOL.execute(() -> {
|
|
||||||
if (players.isEmpty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
final ByteBuf buffer = PacketUtils.writePacket(serverPacket);
|
|
||||||
for (Player player : players) {
|
|
||||||
final PlayerConnection playerConnection = player.getPlayerConnection();
|
|
||||||
if (PlayerUtils.isNettyClient(player)) {
|
|
||||||
playerConnection.writePacket(buffer, true);
|
|
||||||
} else {
|
|
||||||
playerConnection.sendPacket(serverPacket);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buffer.release();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a {@link ServerPacket} and send it to a {@link PlayerConnection}.
|
|
||||||
* <p>
|
|
||||||
* WARNING: should not be used if the packet receive order is important
|
|
||||||
*
|
|
||||||
* @param playerConnection the connection to send the packet to
|
|
||||||
* @param serverPacket the packet to write and send
|
|
||||||
*/
|
|
||||||
public static void writeAndSend(@NotNull PlayerConnection playerConnection, @NotNull ServerPacket serverPacket) {
|
|
||||||
PACKET_WRITER_POOL.execute(() -> {
|
|
||||||
if (PlayerUtils.isNettyClient(playerConnection)) {
|
|
||||||
final ByteBuf buffer = PacketUtils.writePacket(serverPacket);
|
|
||||||
buffer.retain();
|
|
||||||
playerConnection.writePacket(buffer, false);
|
|
||||||
buffer.release();
|
|
||||||
} else {
|
|
||||||
playerConnection.sendPacket(serverPacket);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a {@link ServerPacket} and send it to a {@link Player}.
|
|
||||||
* <p>
|
|
||||||
* WARNING: should not be used if the packet receive order is important
|
|
||||||
*
|
|
||||||
* @param player the player to send the packet to
|
|
||||||
* @param serverPacket the packet to write and send
|
|
||||||
*/
|
|
||||||
public static void writeAndSend(@NotNull Player player, @NotNull ServerPacket serverPacket) {
|
|
||||||
final PlayerConnection playerConnection = player.getPlayerConnection();
|
|
||||||
writeAndSend(playerConnection, serverPacket);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -5,6 +5,9 @@ import io.netty.channel.*;
|
|||||||
import io.netty.channel.epoll.Epoll;
|
import io.netty.channel.epoll.Epoll;
|
||||||
import io.netty.channel.epoll.EpollEventLoopGroup;
|
import io.netty.channel.epoll.EpollEventLoopGroup;
|
||||||
import io.netty.channel.epoll.EpollServerSocketChannel;
|
import io.netty.channel.epoll.EpollServerSocketChannel;
|
||||||
|
import io.netty.channel.kqueue.KQueue;
|
||||||
|
import io.netty.channel.kqueue.KQueueEventLoopGroup;
|
||||||
|
import io.netty.channel.kqueue.KQueueServerSocketChannel;
|
||||||
import io.netty.channel.nio.NioEventLoopGroup;
|
import io.netty.channel.nio.NioEventLoopGroup;
|
||||||
import io.netty.channel.socket.ServerSocketChannel;
|
import io.netty.channel.socket.ServerSocketChannel;
|
||||||
import io.netty.channel.socket.SocketChannel;
|
import io.netty.channel.socket.SocketChannel;
|
||||||
@ -29,24 +32,29 @@ public class NettyServer {
|
|||||||
private String address;
|
private String address;
|
||||||
private int port;
|
private int port;
|
||||||
|
|
||||||
public NettyServer(PacketProcessor packetProcessor) {
|
public NettyServer(@NotNull PacketProcessor packetProcessor) {
|
||||||
Class<? extends ServerChannel> channel;
|
Class<? extends ServerChannel> channel;
|
||||||
|
|
||||||
if (Epoll.isAvailable()) {
|
if (Epoll.isAvailable()) {
|
||||||
boss = new EpollEventLoopGroup(2);
|
boss = new EpollEventLoopGroup(2);
|
||||||
worker = new EpollEventLoopGroup();
|
worker = new EpollEventLoopGroup(); // thread count = core * 2
|
||||||
|
|
||||||
channel = EpollServerSocketChannel.class;
|
channel = EpollServerSocketChannel.class;
|
||||||
|
} else if (KQueue.isAvailable()) {
|
||||||
|
boss = new KQueueEventLoopGroup(2);
|
||||||
|
worker = new KQueueEventLoopGroup(); // thread count = core * 2
|
||||||
|
|
||||||
|
channel = KQueueServerSocketChannel.class;
|
||||||
} else {
|
} else {
|
||||||
boss = new NioEventLoopGroup(2);
|
boss = new NioEventLoopGroup(2);
|
||||||
worker = new NioEventLoopGroup();
|
worker = new NioEventLoopGroup(); // thread count = core * 2
|
||||||
|
|
||||||
channel = NioServerSocketChannel.class;
|
channel = NioServerSocketChannel.class;
|
||||||
}
|
}
|
||||||
|
|
||||||
bootstrap = new ServerBootstrap();
|
bootstrap = new ServerBootstrap()
|
||||||
bootstrap.group(boss, worker);
|
.group(boss, worker)
|
||||||
bootstrap.channel(channel);
|
.channel(channel);
|
||||||
|
|
||||||
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
|
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||||
protected void initChannel(@NotNull SocketChannel ch) {
|
protected void initChannel(@NotNull SocketChannel ch) {
|
||||||
@ -55,10 +63,11 @@ public class NettyServer {
|
|||||||
|
|
||||||
ChannelPipeline pipeline = ch.pipeline();
|
ChannelPipeline pipeline = ch.pipeline();
|
||||||
|
|
||||||
|
// First check should verify if the packet is a legacy ping (from 1.6 version and earlier)
|
||||||
pipeline.addLast("legacy-ping", new LegacyPingHandler());
|
pipeline.addLast("legacy-ping", new LegacyPingHandler());
|
||||||
|
|
||||||
// Adds packetLength at start | Reads framed bytebuf
|
// Adds packetLength at start | Reads framed bytebuf
|
||||||
pipeline.addLast("framer", new PacketFramer());
|
pipeline.addLast("framer", new PacketFramer(packetProcessor));
|
||||||
|
|
||||||
// Reads bytebuf and creating inbound packet
|
// Reads bytebuf and creating inbound packet
|
||||||
pipeline.addLast("decoder", new PacketDecoder());
|
pipeline.addLast("decoder", new PacketDecoder());
|
||||||
|
@ -2,7 +2,6 @@ package net.minestom.server.network.netty.channel;
|
|||||||
|
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.channel.SimpleChannelInboundHandler;
|
import io.netty.channel.SimpleChannelInboundHandler;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import net.minestom.server.MinecraftServer;
|
import net.minestom.server.MinecraftServer;
|
||||||
import net.minestom.server.entity.Player;
|
import net.minestom.server.entity.Player;
|
||||||
import net.minestom.server.network.ConnectionManager;
|
import net.minestom.server.network.ConnectionManager;
|
||||||
@ -10,14 +9,17 @@ import net.minestom.server.network.PacketProcessor;
|
|||||||
import net.minestom.server.network.netty.packet.InboundPacket;
|
import net.minestom.server.network.netty.packet.InboundPacket;
|
||||||
import net.minestom.server.network.player.PlayerConnection;
|
import net.minestom.server.network.player.PlayerConnection;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
public class ClientChannel extends SimpleChannelInboundHandler<InboundPacket> {
|
public class ClientChannel extends SimpleChannelInboundHandler<InboundPacket> {
|
||||||
|
|
||||||
|
private final static Logger LOGGER = LoggerFactory.getLogger(ClientChannel.class);
|
||||||
|
|
||||||
private final ConnectionManager connectionManager = MinecraftServer.getConnectionManager();
|
private final ConnectionManager connectionManager = MinecraftServer.getConnectionManager();
|
||||||
private final PacketProcessor packetProcessor;
|
private final PacketProcessor packetProcessor;
|
||||||
|
|
||||||
public ClientChannel(PacketProcessor packetProcessor) {
|
public ClientChannel(@NotNull PacketProcessor packetProcessor) {
|
||||||
this.packetProcessor = packetProcessor;
|
this.packetProcessor = packetProcessor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,9 +36,10 @@ public class ClientChannel extends SimpleChannelInboundHandler<InboundPacket> {
|
|||||||
final int availableBytes = packet.body.readableBytes();
|
final int availableBytes = packet.body.readableBytes();
|
||||||
|
|
||||||
if (availableBytes > 0) {
|
if (availableBytes > 0) {
|
||||||
// TODO log4j2
|
final PlayerConnection playerConnection = packetProcessor.getPlayerConnection(ctx);
|
||||||
System.err.println("WARNING: Packet 0x" + Integer.toHexString(packet.packetId)
|
|
||||||
+ " not fully read (" + availableBytes + " bytes left)");
|
LOGGER.warn("WARNING: Packet 0x" + Integer.toHexString(packet.packetId)
|
||||||
|
+ " not fully read (" + availableBytes + " bytes left), " + playerConnection);
|
||||||
|
|
||||||
packet.body.skipBytes(availableBytes);
|
packet.body.skipBytes(availableBytes);
|
||||||
}
|
}
|
||||||
@ -60,7 +63,7 @@ public class ClientChannel extends SimpleChannelInboundHandler<InboundPacket> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||||
log.info(cause.getMessage());
|
LOGGER.info(cause.getMessage());
|
||||||
cause.printStackTrace();
|
cause.printStackTrace();
|
||||||
ctx.close();
|
ctx.close();
|
||||||
}
|
}
|
||||||
|
@ -27,45 +27,38 @@ import java.util.List;
|
|||||||
import java.util.zip.Deflater;
|
import java.util.zip.Deflater;
|
||||||
import java.util.zip.Inflater;
|
import java.util.zip.Inflater;
|
||||||
|
|
||||||
// TODO Optimize
|
|
||||||
public class PacketCompressor extends ByteToMessageCodec<ByteBuf> {
|
public class PacketCompressor extends ByteToMessageCodec<ByteBuf> {
|
||||||
|
|
||||||
private final byte[] buffer = new byte[8192];
|
|
||||||
|
|
||||||
private final int threshold;
|
private final int threshold;
|
||||||
|
|
||||||
private final Inflater inflater;
|
private byte[] buffer = new byte[8192];
|
||||||
private final Deflater deflater;
|
|
||||||
|
private Deflater deflater = new Deflater();
|
||||||
|
private Inflater inflater = new Inflater();
|
||||||
|
|
||||||
public PacketCompressor(int threshold) {
|
public PacketCompressor(int threshold) {
|
||||||
this.inflater = new Inflater();
|
|
||||||
this.deflater = new Deflater();
|
|
||||||
|
|
||||||
this.threshold = threshold;
|
this.threshold = threshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void encode(ChannelHandlerContext ctx, ByteBuf from, ByteBuf to) {
|
protected void encode(ChannelHandlerContext ctx, ByteBuf from, ByteBuf to) {
|
||||||
int i = from.readableBytes();
|
final int packetLength = from.readableBytes();
|
||||||
|
|
||||||
if (i < this.threshold) {
|
if (packetLength < this.threshold) {
|
||||||
Utils.writeVarIntBuf(to, 0);
|
Utils.writeVarIntBuf(to, 0);
|
||||||
to.writeBytes(from);
|
to.writeBytes(from);
|
||||||
} else {
|
} else {
|
||||||
byte[] abyte = new byte[i];
|
Utils.writeVarIntBuf(to, packetLength);
|
||||||
from.readBytes(abyte);
|
|
||||||
|
|
||||||
Utils.writeVarIntBuf(to, abyte.length);
|
deflater.setInput(from.nioBuffer());
|
||||||
this.deflater.setInput(abyte, 0, i);
|
deflater.finish();
|
||||||
this.deflater.finish();
|
|
||||||
|
|
||||||
while (!this.deflater.finished()) {
|
while (!deflater.finished()) {
|
||||||
int j = this.deflater.deflate(this.buffer);
|
final int length = deflater.deflate(buffer);
|
||||||
|
to.writeBytes(buffer, 0, length);
|
||||||
to.writeBytes(this.buffer, 0, j);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.deflater.reset();
|
deflater.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,16 +78,18 @@ public class PacketCompressor extends ByteToMessageCodec<ByteBuf> {
|
|||||||
throw new DecoderException("Badly compressed packet - size of " + i + " is larger than protocol maximum of 2097152");
|
throw new DecoderException("Badly compressed packet - size of " + i + " is larger than protocol maximum of 2097152");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO optimize to do not initialize arrays each time
|
||||||
|
|
||||||
byte[] abyte = new byte[buf.readableBytes()];
|
byte[] abyte = new byte[buf.readableBytes()];
|
||||||
buf.readBytes(abyte);
|
buf.readBytes(abyte);
|
||||||
|
|
||||||
this.inflater.setInput(abyte);
|
inflater.setInput(abyte);
|
||||||
byte[] abyte1 = new byte[i];
|
byte[] abyte1 = new byte[i];
|
||||||
|
|
||||||
this.inflater.inflate(abyte1);
|
inflater.inflate(abyte1);
|
||||||
out.add(Unpooled.wrappedBuffer(abyte1));
|
out.add(Unpooled.wrappedBuffer(abyte1));
|
||||||
|
|
||||||
this.inflater.reset();
|
inflater.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,25 @@ import io.netty.buffer.ByteBuf;
|
|||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.handler.codec.ByteToMessageCodec;
|
import io.netty.handler.codec.ByteToMessageCodec;
|
||||||
import io.netty.handler.codec.CorruptedFrameException;
|
import io.netty.handler.codec.CorruptedFrameException;
|
||||||
|
import net.minestom.server.MinecraftServer;
|
||||||
|
import net.minestom.server.network.PacketProcessor;
|
||||||
|
import net.minestom.server.network.player.PlayerConnection;
|
||||||
import net.minestom.server.utils.Utils;
|
import net.minestom.server.utils.Utils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class PacketFramer extends ByteToMessageCodec<ByteBuf> {
|
public class PacketFramer extends ByteToMessageCodec<ByteBuf> {
|
||||||
|
|
||||||
|
public final static Logger LOGGER = LoggerFactory.getLogger(PacketFramer.class);
|
||||||
|
|
||||||
|
private final PacketProcessor packetProcessor;
|
||||||
|
|
||||||
|
public PacketFramer(PacketProcessor packetProcessor) {
|
||||||
|
this.packetProcessor = packetProcessor;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void encode(ChannelHandlerContext ctx, ByteBuf from, ByteBuf to) {
|
protected void encode(ChannelHandlerContext ctx, ByteBuf from, ByteBuf to) {
|
||||||
final int packetSize = from.readableBytes();
|
final int packetSize = from.readableBytes();
|
||||||
@ -40,14 +53,26 @@ public class PacketFramer extends ByteToMessageCodec<ByteBuf> {
|
|||||||
if (b >= 0) {
|
if (b >= 0) {
|
||||||
buf.resetReaderIndex();
|
buf.resetReaderIndex();
|
||||||
|
|
||||||
final int j = Utils.readVarInt(buf);
|
final int packetSize = Utils.readVarInt(buf);
|
||||||
|
|
||||||
if (buf.readableBytes() < j) {
|
// Max packet size check
|
||||||
|
if (packetSize >= MinecraftServer.getMaxPacketSize()) {
|
||||||
|
final PlayerConnection playerConnection = packetProcessor.getPlayerConnection(ctx);
|
||||||
|
if (playerConnection != null) {
|
||||||
|
final String identifier = playerConnection.getIdentifier();
|
||||||
|
LOGGER.warn("An user (" + identifier + ") sent a packet over the maximum size (" + packetSize + ")");
|
||||||
|
} else {
|
||||||
|
LOGGER.warn("An unregistered user sent a packet over the maximum size (" + packetSize + ")");
|
||||||
|
}
|
||||||
|
ctx.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buf.readableBytes() < packetSize) {
|
||||||
buf.resetReaderIndex();
|
buf.resetReaderIndex();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
out.add(buf.readRetainedSlice(j));
|
out.add(buf.readRetainedSlice(packetSize));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
package net.minestom.server.network.netty.packet;
|
package net.minestom.server.network.netty.packet;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public class InboundPacket {
|
public class InboundPacket {
|
||||||
|
|
||||||
public final int packetId;
|
public final int packetId;
|
||||||
public final ByteBuf body;
|
public final ByteBuf body;
|
||||||
|
|
||||||
public InboundPacket(int id, ByteBuf body) {
|
public InboundPacket(int id, @NotNull ByteBuf body) {
|
||||||
this.packetId = id;
|
this.packetId = id;
|
||||||
this.body = body;
|
this.body = body;
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ public abstract class ClientPlayPacket implements ClientPacket {
|
|||||||
* @param player the player who sent the packet
|
* @param player the player who sent the packet
|
||||||
*/
|
*/
|
||||||
public void process(@NotNull Player player) {
|
public void process(@NotNull Player player) {
|
||||||
PACKET_LISTENER_MANAGER.process(this, player);
|
PACKET_LISTENER_MANAGER.processClientPacket(this, player);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,19 @@
|
|||||||
package net.minestom.server.network.packet.client.handler;
|
package net.minestom.server.network.packet.client.handler;
|
||||||
|
|
||||||
import net.minestom.server.network.packet.client.ClientPacket;
|
import net.minestom.server.network.packet.client.ClientPacket;
|
||||||
|
import net.minestom.server.utils.binary.BinaryReader;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains registered packets and a way to instantiate them.
|
||||||
|
* <p>
|
||||||
|
* Packets are register using {@link #register(int, ClientPacketSupplier)}
|
||||||
|
* (you can override a packet id even if not recommended and not officially supported) and retrieved with {@link #getPacketInstance(int)}.
|
||||||
|
* <p>
|
||||||
|
* If you want to fill the packet from a buffer, consider using {@link ClientPacket#read(BinaryReader)} after getting the packet instance.
|
||||||
|
*/
|
||||||
public class ClientPacketsHandler {
|
public class ClientPacketsHandler {
|
||||||
|
|
||||||
// Max packet id
|
// Max packet id
|
||||||
|
@ -3,6 +3,7 @@ package net.minestom.server.network.packet.client.handshake;
|
|||||||
import net.minestom.server.MinecraftServer;
|
import net.minestom.server.MinecraftServer;
|
||||||
import net.minestom.server.chat.ChatColor;
|
import net.minestom.server.chat.ChatColor;
|
||||||
import net.minestom.server.chat.ColoredText;
|
import net.minestom.server.chat.ColoredText;
|
||||||
|
import net.minestom.server.entity.PlayerSkin;
|
||||||
import net.minestom.server.extras.bungee.BungeeCordProxy;
|
import net.minestom.server.extras.bungee.BungeeCordProxy;
|
||||||
import net.minestom.server.network.ConnectionState;
|
import net.minestom.server.network.ConnectionState;
|
||||||
import net.minestom.server.network.packet.client.ClientPreplayPacket;
|
import net.minestom.server.network.packet.client.ClientPreplayPacket;
|
||||||
@ -13,6 +14,7 @@ import net.minestom.server.utils.binary.BinaryReader;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
public class HandshakePacket implements ClientPreplayPacket {
|
public class HandshakePacket implements ClientPreplayPacket {
|
||||||
|
|
||||||
@ -31,7 +33,7 @@ public class HandshakePacket implements ClientPreplayPacket {
|
|||||||
@Override
|
@Override
|
||||||
public void read(@NotNull BinaryReader reader) {
|
public void read(@NotNull BinaryReader reader) {
|
||||||
this.protocolVersion = reader.readVarInt();
|
this.protocolVersion = reader.readVarInt();
|
||||||
this.serverAddress = reader.readSizedString();
|
this.serverAddress = reader.readSizedString(BungeeCordProxy.isEnabled() ? Short.MAX_VALUE : 255);
|
||||||
this.serverPort = reader.readUnsignedShort();
|
this.serverPort = reader.readUnsignedShort();
|
||||||
this.nextState = reader.readVarInt();
|
this.nextState = reader.readVarInt();
|
||||||
}
|
}
|
||||||
@ -39,6 +41,7 @@ public class HandshakePacket implements ClientPreplayPacket {
|
|||||||
@Override
|
@Override
|
||||||
public void process(@NotNull PlayerConnection connection) {
|
public void process(@NotNull PlayerConnection connection) {
|
||||||
|
|
||||||
|
// Bungee support (IP forwarding)
|
||||||
if (BungeeCordProxy.isEnabled() && connection instanceof NettyPlayerConnection) {
|
if (BungeeCordProxy.isEnabled() && connection instanceof NettyPlayerConnection) {
|
||||||
NettyPlayerConnection nettyPlayerConnection = (NettyPlayerConnection) connection;
|
NettyPlayerConnection nettyPlayerConnection = (NettyPlayerConnection) connection;
|
||||||
|
|
||||||
@ -48,8 +51,25 @@ public class HandshakePacket implements ClientPreplayPacket {
|
|||||||
if (split.length == 3 || split.length == 4) {
|
if (split.length == 3 || split.length == 4) {
|
||||||
this.serverAddress = split[0];
|
this.serverAddress = split[0];
|
||||||
|
|
||||||
final SocketAddress socketAddress = new java.net.InetSocketAddress(split[1], ((java.net.InetSocketAddress) connection.getRemoteAddress()).getPort());
|
final SocketAddress socketAddress = new java.net.InetSocketAddress(split[1],
|
||||||
|
((java.net.InetSocketAddress) connection.getRemoteAddress()).getPort());
|
||||||
nettyPlayerConnection.setRemoteAddress(socketAddress);
|
nettyPlayerConnection.setRemoteAddress(socketAddress);
|
||||||
|
|
||||||
|
UUID playerUuid = UUID.fromString(
|
||||||
|
split[2]
|
||||||
|
.replaceFirst(
|
||||||
|
"(\\p{XDigit}{8})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}+)", "$1-$2-$3-$4-$5"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
PlayerSkin playerSkin = null;
|
||||||
|
|
||||||
|
if (split.length == 4) {
|
||||||
|
playerSkin = BungeeCordProxy.readSkin(split[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
nettyPlayerConnection.UNSAFE_setBungeeUuid(playerUuid);
|
||||||
|
nettyPlayerConnection.UNSAFE_setBungeeSkin(playerSkin);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
nettyPlayerConnection.sendPacket(new LoginDisconnectPacket(INVALID_BUNGEE_FORWARDING));
|
nettyPlayerConnection.sendPacket(new LoginDisconnectPacket(INVALID_BUNGEE_FORWARDING));
|
||||||
nettyPlayerConnection.disconnect();
|
nettyPlayerConnection.disconnect();
|
||||||
|
@ -4,6 +4,7 @@ import com.mojang.authlib.GameProfile;
|
|||||||
import com.mojang.authlib.exceptions.AuthenticationUnavailableException;
|
import com.mojang.authlib.exceptions.AuthenticationUnavailableException;
|
||||||
import net.minestom.server.MinecraftServer;
|
import net.minestom.server.MinecraftServer;
|
||||||
import net.minestom.server.data.type.array.ByteArrayData;
|
import net.minestom.server.data.type.array.ByteArrayData;
|
||||||
|
import net.minestom.server.extras.MojangAuth;
|
||||||
import net.minestom.server.extras.mojangAuth.MojangCrypt;
|
import net.minestom.server.extras.mojangAuth.MojangCrypt;
|
||||||
import net.minestom.server.network.packet.client.ClientPreplayPacket;
|
import net.minestom.server.network.packet.client.ClientPreplayPacket;
|
||||||
import net.minestom.server.network.player.NettyPlayerConnection;
|
import net.minestom.server.network.player.NettyPlayerConnection;
|
||||||
@ -38,25 +39,25 @@ public class EncryptionResponsePacket implements ClientPreplayPacket {
|
|||||||
try {
|
try {
|
||||||
final String loginUsername = nettyConnection.getLoginUsername();
|
final String loginUsername = nettyConnection.getLoginUsername();
|
||||||
if (!Arrays.equals(nettyConnection.getNonce(), getNonce())) {
|
if (!Arrays.equals(nettyConnection.getNonce(), getNonce())) {
|
||||||
MinecraftServer.getLOGGER().error(loginUsername + " tried to login with an invalid nonce!");
|
MinecraftServer.LOGGER.error(loginUsername + " tried to login with an invalid nonce!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!loginUsername.isEmpty()) {
|
if (!loginUsername.isEmpty()) {
|
||||||
|
|
||||||
final byte[] digestedData = MojangCrypt.digestData("", MinecraftServer.getKeyPair().getPublic(), getSecretKey());
|
final byte[] digestedData = MojangCrypt.digestData("", MojangAuth.getKeyPair().getPublic(), getSecretKey());
|
||||||
|
|
||||||
if (digestedData == null) {
|
if (digestedData == null) {
|
||||||
// Incorrect key, probably because of the client
|
// Incorrect key, probably because of the client
|
||||||
MinecraftServer.getLOGGER().error("Connection " + nettyConnection.getRemoteAddress() + " failed initializing encryption.");
|
MinecraftServer.LOGGER.error("Connection " + nettyConnection.getRemoteAddress() + " failed initializing encryption.");
|
||||||
connection.disconnect();
|
connection.disconnect();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final String string3 = new BigInteger(digestedData).toString(16);
|
final String string3 = new BigInteger(digestedData).toString(16);
|
||||||
final GameProfile gameProfile = MinecraftServer.getSessionService().hasJoinedServer(new GameProfile(null, loginUsername), string3);
|
final GameProfile gameProfile = MojangAuth.getSessionService().hasJoinedServer(new GameProfile(null, loginUsername), string3);
|
||||||
nettyConnection.setEncryptionKey(getSecretKey());
|
nettyConnection.setEncryptionKey(getSecretKey());
|
||||||
|
|
||||||
MinecraftServer.getLOGGER().info("UUID of player {} is {}", loginUsername, gameProfile.getId());
|
MinecraftServer.LOGGER.info("UUID of player {} is {}", loginUsername, gameProfile.getId());
|
||||||
CONNECTION_MANAGER.startPlayState(connection, gameProfile.getId(), gameProfile.getName());
|
CONNECTION_MANAGER.startPlayState(connection, gameProfile.getId(), gameProfile.getName());
|
||||||
}
|
}
|
||||||
} catch (AuthenticationUnavailableException e) {
|
} catch (AuthenticationUnavailableException e) {
|
||||||
@ -73,10 +74,11 @@ public class EncryptionResponsePacket implements ClientPreplayPacket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public SecretKey getSecretKey() {
|
public SecretKey getSecretKey() {
|
||||||
return MojangCrypt.decryptByteToSecretKey(MinecraftServer.getKeyPair().getPrivate(), sharedSecret);
|
return MojangCrypt.decryptByteToSecretKey(MojangAuth.getKeyPair().getPrivate(), sharedSecret);
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getNonce() {
|
public byte[] getNonce() {
|
||||||
return MinecraftServer.getKeyPair().getPrivate() == null ? this.verifyToken : MojangCrypt.decryptUsingKey(MinecraftServer.getKeyPair().getPrivate(), this.verifyToken);
|
return MojangAuth.getKeyPair().getPrivate() == null ?
|
||||||
|
this.verifyToken : MojangCrypt.decryptUsingKey(MojangAuth.getKeyPair().getPrivate(), this.verifyToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@ package net.minestom.server.network.packet.client.login;
|
|||||||
import net.minestom.server.MinecraftServer;
|
import net.minestom.server.MinecraftServer;
|
||||||
import net.minestom.server.chat.ChatColor;
|
import net.minestom.server.chat.ChatColor;
|
||||||
import net.minestom.server.chat.ColoredText;
|
import net.minestom.server.chat.ColoredText;
|
||||||
|
import net.minestom.server.entity.Player;
|
||||||
|
import net.minestom.server.entity.PlayerSkin;
|
||||||
import net.minestom.server.extras.velocity.VelocityProxy;
|
import net.minestom.server.extras.velocity.VelocityProxy;
|
||||||
import net.minestom.server.network.ConnectionManager;
|
import net.minestom.server.network.ConnectionManager;
|
||||||
import net.minestom.server.network.packet.client.ClientPreplayPacket;
|
import net.minestom.server.network.packet.client.ClientPreplayPacket;
|
||||||
@ -37,7 +39,11 @@ public class LoginPluginResponsePacket implements ClientPreplayPacket {
|
|||||||
|
|
||||||
if (channel != null) {
|
if (channel != null) {
|
||||||
boolean success = false;
|
boolean success = false;
|
||||||
|
|
||||||
SocketAddress socketAddress = null;
|
SocketAddress socketAddress = null;
|
||||||
|
UUID playerUuid = null;
|
||||||
|
String playerUsername = null;
|
||||||
|
PlayerSkin playerSkin = null;
|
||||||
|
|
||||||
// Velocity
|
// Velocity
|
||||||
if (VelocityProxy.isEnabled() && channel.equals(VelocityProxy.PLAYER_INFO_CHANNEL)) {
|
if (VelocityProxy.isEnabled() && channel.equals(VelocityProxy.PLAYER_INFO_CHANNEL)) {
|
||||||
@ -49,6 +55,12 @@ public class LoginPluginResponsePacket implements ClientPreplayPacket {
|
|||||||
final InetAddress address = VelocityProxy.readAddress(reader);
|
final InetAddress address = VelocityProxy.readAddress(reader);
|
||||||
final int port = ((java.net.InetSocketAddress) connection.getRemoteAddress()).getPort();
|
final int port = ((java.net.InetSocketAddress) connection.getRemoteAddress()).getPort();
|
||||||
socketAddress = new InetSocketAddress(address, port);
|
socketAddress = new InetSocketAddress(address, port);
|
||||||
|
|
||||||
|
playerUuid = reader.readUuid();
|
||||||
|
playerUsername = reader.readSizedString(16);
|
||||||
|
|
||||||
|
playerSkin = VelocityProxy.readSkin(reader);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,12 +69,16 @@ public class LoginPluginResponsePacket implements ClientPreplayPacket {
|
|||||||
if (socketAddress != null) {
|
if (socketAddress != null) {
|
||||||
nettyPlayerConnection.setRemoteAddress(socketAddress);
|
nettyPlayerConnection.setRemoteAddress(socketAddress);
|
||||||
}
|
}
|
||||||
|
if (playerUsername != null) {
|
||||||
|
nettyPlayerConnection.UNSAFE_setLoginUsername(playerUsername);
|
||||||
|
}
|
||||||
|
|
||||||
// Proxy usage always mean that the server is in offline mode
|
|
||||||
final String username = nettyPlayerConnection.getLoginUsername();
|
final String username = nettyPlayerConnection.getLoginUsername();
|
||||||
final UUID playerUuid = CONNECTION_MANAGER.getPlayerConnectionUuid(connection, username);
|
final UUID uuid = playerUuid != null ?
|
||||||
|
playerUuid : CONNECTION_MANAGER.getPlayerConnectionUuid(connection, username);
|
||||||
|
|
||||||
CONNECTION_MANAGER.startPlayState(connection, playerUuid, username);
|
Player player = CONNECTION_MANAGER.startPlayState(connection, uuid, username);
|
||||||
|
player.setSkin(playerSkin);
|
||||||
} else {
|
} else {
|
||||||
LoginDisconnectPacket disconnectPacket = new LoginDisconnectPacket(INVALID_PROXY_RESPONSE);
|
LoginDisconnectPacket disconnectPacket = new LoginDisconnectPacket(INVALID_PROXY_RESPONSE);
|
||||||
nettyPlayerConnection.sendPacket(disconnectPacket);
|
nettyPlayerConnection.sendPacket(disconnectPacket);
|
||||||
|
@ -3,7 +3,9 @@ package net.minestom.server.network.packet.client.login;
|
|||||||
import net.minestom.server.MinecraftServer;
|
import net.minestom.server.MinecraftServer;
|
||||||
import net.minestom.server.chat.ChatColor;
|
import net.minestom.server.chat.ChatColor;
|
||||||
import net.minestom.server.chat.ColoredText;
|
import net.minestom.server.chat.ColoredText;
|
||||||
|
import net.minestom.server.entity.Player;
|
||||||
import net.minestom.server.extras.MojangAuth;
|
import net.minestom.server.extras.MojangAuth;
|
||||||
|
import net.minestom.server.extras.bungee.BungeeCordProxy;
|
||||||
import net.minestom.server.extras.velocity.VelocityProxy;
|
import net.minestom.server.extras.velocity.VelocityProxy;
|
||||||
import net.minestom.server.network.ConnectionState;
|
import net.minestom.server.network.ConnectionState;
|
||||||
import net.minestom.server.network.packet.client.ClientPreplayPacket;
|
import net.minestom.server.network.packet.client.ClientPreplayPacket;
|
||||||
@ -27,8 +29,10 @@ public class LoginStartPacket implements ClientPreplayPacket {
|
|||||||
@Override
|
@Override
|
||||||
public void process(@NotNull PlayerConnection connection) {
|
public void process(@NotNull PlayerConnection connection) {
|
||||||
|
|
||||||
|
final boolean isNettyClient = connection instanceof NettyPlayerConnection;
|
||||||
|
|
||||||
// Cache the login username and start compression if enabled
|
// Cache the login username and start compression if enabled
|
||||||
if (connection instanceof NettyPlayerConnection) {
|
if (isNettyClient) {
|
||||||
NettyPlayerConnection nettyPlayerConnection = (NettyPlayerConnection) connection;
|
NettyPlayerConnection nettyPlayerConnection = (NettyPlayerConnection) connection;
|
||||||
nettyPlayerConnection.UNSAFE_setLoginUsername(username);
|
nettyPlayerConnection.UNSAFE_setLoginUsername(username);
|
||||||
|
|
||||||
@ -40,7 +44,7 @@ public class LoginStartPacket implements ClientPreplayPacket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Proxy support (only for netty clients)
|
// Proxy support (only for netty clients)
|
||||||
if (connection instanceof NettyPlayerConnection) {
|
if (isNettyClient) {
|
||||||
final NettyPlayerConnection nettyPlayerConnection = (NettyPlayerConnection) connection;
|
final NettyPlayerConnection nettyPlayerConnection = (NettyPlayerConnection) connection;
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -65,7 +69,7 @@ public class LoginStartPacket implements ClientPreplayPacket {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (MojangAuth.isUsingMojangAuth() && connection instanceof NettyPlayerConnection) {
|
if (MojangAuth.isEnabled() && isNettyClient) {
|
||||||
// Mojang auth
|
// Mojang auth
|
||||||
if (CONNECTION_MANAGER.getPlayer(username) != null) {
|
if (CONNECTION_MANAGER.getPlayer(username) != null) {
|
||||||
connection.sendPacket(new LoginDisconnectPacket(ALREADY_CONNECTED_JSON));
|
connection.sendPacket(new LoginDisconnectPacket(ALREADY_CONNECTED_JSON));
|
||||||
@ -79,16 +83,22 @@ public class LoginStartPacket implements ClientPreplayPacket {
|
|||||||
EncryptionRequestPacket encryptionRequestPacket = new EncryptionRequestPacket(nettyPlayerConnection);
|
EncryptionRequestPacket encryptionRequestPacket = new EncryptionRequestPacket(nettyPlayerConnection);
|
||||||
nettyPlayerConnection.sendPacket(encryptionRequestPacket);
|
nettyPlayerConnection.sendPacket(encryptionRequestPacket);
|
||||||
} else {
|
} else {
|
||||||
|
final boolean bungee = BungeeCordProxy.isEnabled();
|
||||||
// Offline
|
// Offline
|
||||||
final UUID playerUuid = CONNECTION_MANAGER.getPlayerConnectionUuid(connection, username);
|
final UUID playerUuid = bungee && isNettyClient ?
|
||||||
|
((NettyPlayerConnection) connection).getBungeeUuid() :
|
||||||
|
CONNECTION_MANAGER.getPlayerConnectionUuid(connection, username);
|
||||||
|
|
||||||
CONNECTION_MANAGER.startPlayState(connection, playerUuid, username);
|
Player player = CONNECTION_MANAGER.startPlayState(connection, playerUuid, username);
|
||||||
|
if (bungee && isNettyClient) {
|
||||||
|
player.setSkin(((NettyPlayerConnection) connection).getBungeeSkin());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void read(@NotNull BinaryReader reader) {
|
public void read(@NotNull BinaryReader reader) {
|
||||||
this.username = reader.readSizedString();
|
this.username = reader.readSizedString(16);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ public class ClientAdvancementTabPacket extends ClientPlayPacket {
|
|||||||
this.action = AdvancementAction.values()[reader.readVarInt()];
|
this.action = AdvancementAction.values()[reader.readVarInt()];
|
||||||
|
|
||||||
if (action == AdvancementAction.OPENED_TAB) {
|
if (action == AdvancementAction.OPENED_TAB) {
|
||||||
this.tabIdentifier = reader.readSizedString();
|
this.tabIdentifier = reader.readSizedString(256);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,6 @@ public class ClientChatMessagePacket extends ClientPlayPacket {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void read(@NotNull BinaryReader reader) {
|
public void read(@NotNull BinaryReader reader) {
|
||||||
this.message = reader.readSizedString();
|
this.message = reader.readSizedString(256);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ public class ClientCraftRecipeRequest extends ClientPlayPacket {
|
|||||||
@Override
|
@Override
|
||||||
public void read(@NotNull BinaryReader reader) {
|
public void read(@NotNull BinaryReader reader) {
|
||||||
this.windowId = reader.readByte();
|
this.windowId = reader.readByte();
|
||||||
this.recipe = reader.readSizedString();
|
this.recipe = reader.readSizedString(256);
|
||||||
this.makeAll = reader.readBoolean();
|
this.makeAll = reader.readBoolean();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,6 @@ public class ClientNameItemPacket extends ClientPlayPacket {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void read(@NotNull BinaryReader reader) {
|
public void read(@NotNull BinaryReader reader) {
|
||||||
this.itemName = reader.readSizedString();
|
this.itemName = reader.readSizedString(Short.MAX_VALUE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ public class ClientPluginMessagePacket extends ClientPlayPacket {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void read(@NotNull BinaryReader reader) {
|
public void read(@NotNull BinaryReader reader) {
|
||||||
this.channel = reader.readSizedString();
|
this.channel = reader.readSizedString(256);
|
||||||
this.data = reader.getRemainingBytes();
|
this.data = reader.getRemainingBytes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ public class ClientRecipeBookData extends ClientPlayPacket {
|
|||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 0:
|
case 0:
|
||||||
this.recipeId = reader.readSizedString();
|
this.recipeId = reader.readSizedString(256);
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
this.craftingRecipeBookOpen = reader.readBoolean();
|
this.craftingRecipeBookOpen = reader.readBoolean();
|
||||||
|
@ -16,7 +16,7 @@ public class ClientSettingsPacket extends ClientPlayPacket {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void read(@NotNull BinaryReader reader) {
|
public void read(@NotNull BinaryReader reader) {
|
||||||
this.locale = reader.readSizedString();
|
this.locale = reader.readSizedString(128);
|
||||||
this.viewDistance = reader.readByte();
|
this.viewDistance = reader.readByte();
|
||||||
this.chatMode = Player.ChatMode.values()[reader.readVarInt()];
|
this.chatMode = Player.ChatMode.values()[reader.readVarInt()];
|
||||||
this.chatColors = reader.readBoolean();
|
this.chatColors = reader.readBoolean();
|
||||||
|
@ -12,6 +12,6 @@ public class ClientTabCompletePacket extends ClientPlayPacket {
|
|||||||
@Override
|
@Override
|
||||||
public void read(@NotNull BinaryReader reader) {
|
public void read(@NotNull BinaryReader reader) {
|
||||||
this.transactionId = reader.readVarInt();
|
this.transactionId = reader.readVarInt();
|
||||||
this.text = reader.readSizedString();
|
this.text = reader.readSizedString(Short.MAX_VALUE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ public class ClientUpdateCommandBlockMinecartPacket extends ClientPlayPacket {
|
|||||||
@Override
|
@Override
|
||||||
public void read(@NotNull BinaryReader reader) {
|
public void read(@NotNull BinaryReader reader) {
|
||||||
this.entityId = reader.readVarInt();
|
this.entityId = reader.readVarInt();
|
||||||
this.command = reader.readSizedString();
|
this.command = reader.readSizedString(Short.MAX_VALUE);
|
||||||
this.trackOutput = reader.readBoolean();
|
this.trackOutput = reader.readBoolean();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ public class ClientUpdateCommandBlockPacket extends ClientPlayPacket {
|
|||||||
@Override
|
@Override
|
||||||
public void read(@NotNull BinaryReader reader) {
|
public void read(@NotNull BinaryReader reader) {
|
||||||
this.blockPosition = reader.readBlockPosition();
|
this.blockPosition = reader.readBlockPosition();
|
||||||
this.command = reader.readSizedString();
|
this.command = reader.readSizedString(Short.MAX_VALUE);
|
||||||
this.mode = Mode.values()[reader.readVarInt()];
|
this.mode = Mode.values()[reader.readVarInt()];
|
||||||
this.flags = reader.readByte();
|
this.flags = reader.readByte();
|
||||||
}
|
}
|
||||||
|
@ -16,10 +16,10 @@ public class ClientUpdateSignPacket extends ClientPlayPacket {
|
|||||||
@Override
|
@Override
|
||||||
public void read(@NotNull BinaryReader reader) {
|
public void read(@NotNull BinaryReader reader) {
|
||||||
this.blockPosition = reader.readBlockPosition();
|
this.blockPosition = reader.readBlockPosition();
|
||||||
this.line1 = reader.readSizedString();
|
this.line1 = reader.readSizedString(384);
|
||||||
this.line2 = reader.readSizedString();
|
this.line2 = reader.readSizedString(384);
|
||||||
this.line3 = reader.readSizedString();
|
this.line3 = reader.readSizedString(384);
|
||||||
this.line4 = reader.readSizedString();
|
this.line4 = reader.readSizedString(384);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package net.minestom.server.network.packet.server.login;
|
package net.minestom.server.network.packet.server.login;
|
||||||
|
|
||||||
import net.minestom.server.MinecraftServer;
|
|
||||||
import net.minestom.server.data.type.array.ByteArrayData;
|
import net.minestom.server.data.type.array.ByteArrayData;
|
||||||
|
import net.minestom.server.extras.MojangAuth;
|
||||||
import net.minestom.server.network.packet.server.ServerPacket;
|
import net.minestom.server.network.packet.server.ServerPacket;
|
||||||
import net.minestom.server.network.packet.server.ServerPacketIdentifier;
|
import net.minestom.server.network.packet.server.ServerPacketIdentifier;
|
||||||
import net.minestom.server.network.player.NettyPlayerConnection;
|
import net.minestom.server.network.player.NettyPlayerConnection;
|
||||||
@ -23,7 +23,7 @@ public class EncryptionRequestPacket implements ServerPacket {
|
|||||||
@Override
|
@Override
|
||||||
public void write(@NotNull BinaryWriter writer) {
|
public void write(@NotNull BinaryWriter writer) {
|
||||||
writer.writeSizedString("");
|
writer.writeSizedString("");
|
||||||
final byte[] publicKey = MinecraftServer.getKeyPair().getPublic().getEncoded();
|
final byte[] publicKey = MojangAuth.getKeyPair().getPublic().getEncoded();
|
||||||
ByteArrayData.encodeByteArray(writer, publicKey);
|
ByteArrayData.encodeByteArray(writer, publicKey);
|
||||||
ByteArrayData.encodeByteArray(writer, nonce);
|
ByteArrayData.encodeByteArray(writer, nonce);
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,9 @@ import io.netty.buffer.Unpooled;
|
|||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||||
import net.minestom.server.MinecraftServer;
|
import net.minestom.server.MinecraftServer;
|
||||||
import net.minestom.server.data.Data;
|
import net.minestom.server.data.Data;
|
||||||
import net.minestom.server.instance.Chunk;
|
|
||||||
import net.minestom.server.instance.block.BlockManager;
|
import net.minestom.server.instance.block.BlockManager;
|
||||||
import net.minestom.server.instance.block.CustomBlock;
|
import net.minestom.server.instance.block.CustomBlock;
|
||||||
|
import net.minestom.server.instance.palette.PaletteStorage;
|
||||||
import net.minestom.server.network.packet.server.ServerPacket;
|
import net.minestom.server.network.packet.server.ServerPacket;
|
||||||
import net.minestom.server.network.packet.server.ServerPacketIdentifier;
|
import net.minestom.server.network.packet.server.ServerPacketIdentifier;
|
||||||
import net.minestom.server.utils.BlockPosition;
|
import net.minestom.server.utils.BlockPosition;
|
||||||
@ -28,18 +28,17 @@ public class ChunkDataPacket implements ServerPacket {
|
|||||||
public Biome[] biomes;
|
public Biome[] biomes;
|
||||||
public int chunkX, chunkZ;
|
public int chunkX, chunkZ;
|
||||||
|
|
||||||
public short[] blocksStateId;
|
public PaletteStorage paletteStorage;
|
||||||
public short[] customBlocksId;
|
public PaletteStorage customBlockPaletteStorage;
|
||||||
|
|
||||||
public Set<Integer> blockEntities;
|
public Set<Integer> blockEntities;
|
||||||
public Int2ObjectMap<Data> blocksData;
|
public Int2ObjectMap<Data> blocksData;
|
||||||
//public Chunk chunk;
|
|
||||||
|
|
||||||
public int[] sections;
|
public int[] sections;
|
||||||
|
|
||||||
private static final byte CHUNK_SECTION_COUNT = 16;
|
private static final byte CHUNK_SECTION_COUNT = 16;
|
||||||
private static final int BITS_PER_ENTRY = 15;
|
private static final int MAX_BITS_PER_ENTRY = 16;
|
||||||
private static final int MAX_BUFFER_SIZE = (Short.BYTES + Byte.BYTES + 5 * Byte.BYTES + (4096 * BITS_PER_ENTRY / Long.SIZE * Long.BYTES)) * CHUNK_SECTION_COUNT + 256 * Integer.BYTES;
|
private static final int MAX_BUFFER_SIZE = (Short.BYTES + Byte.BYTES + 5 * Byte.BYTES + (4096 * MAX_BITS_PER_ENTRY / Long.SIZE * Long.BYTES)) * CHUNK_SECTION_COUNT + 256 * Integer.BYTES;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(@NotNull BinaryWriter writer) {
|
public void write(@NotNull BinaryWriter writer) {
|
||||||
@ -51,10 +50,10 @@ public class ChunkDataPacket implements ServerPacket {
|
|||||||
ByteBuf blocks = Unpooled.buffer(MAX_BUFFER_SIZE);
|
ByteBuf blocks = Unpooled.buffer(MAX_BUFFER_SIZE);
|
||||||
for (byte i = 0; i < CHUNK_SECTION_COUNT; i++) {
|
for (byte i = 0; i < CHUNK_SECTION_COUNT; i++) {
|
||||||
if (fullChunk || (sections.length == CHUNK_SECTION_COUNT && sections[i] != 0)) {
|
if (fullChunk || (sections.length == CHUNK_SECTION_COUNT && sections[i] != 0)) {
|
||||||
short[] section = getSection(i);
|
final long[] section = paletteStorage.getSectionBlocks()[i];
|
||||||
if (section != null) { // section contains at least one block
|
if (section.length > 0) { // section contains at least one block
|
||||||
mask |= 1 << i;
|
mask |= 1 << i;
|
||||||
Utils.writeBlocks(blocks, section, BITS_PER_ENTRY);
|
Utils.writeBlocks(blocks, paletteStorage.getPalette(i), section, paletteStorage.getBitsPerEntry());
|
||||||
} else {
|
} else {
|
||||||
mask |= 0;
|
mask |= 0;
|
||||||
}
|
}
|
||||||
@ -106,36 +105,18 @@ public class ChunkDataPacket implements ServerPacket {
|
|||||||
.setInt("y", blockPosition.getY())
|
.setInt("y", blockPosition.getY())
|
||||||
.setInt("z", blockPosition.getZ());
|
.setInt("z", blockPosition.getZ());
|
||||||
|
|
||||||
final short customBlockId = customBlocksId[index];
|
if (customBlockPaletteStorage != null) {
|
||||||
final CustomBlock customBlock = BLOCK_MANAGER.getCustomBlock(customBlockId);
|
final short customBlockId = customBlockPaletteStorage.getBlockAt(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ());
|
||||||
if (customBlock != null) {
|
final CustomBlock customBlock = BLOCK_MANAGER.getCustomBlock(customBlockId);
|
||||||
final Data data = blocksData.get(index);
|
if (customBlock != null) {
|
||||||
customBlock.writeBlockEntity(blockPosition, data, nbt);
|
final Data data = blocksData.get(index);
|
||||||
|
customBlock.writeBlockEntity(blockPosition, data, nbt);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
writer.writeNBT("", nbt);
|
writer.writeNBT("", nbt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private short[] getSection(byte section) {
|
|
||||||
short[] blocks = new short[Chunk.CHUNK_SIZE_X * Chunk.CHUNK_SECTION_SIZE * Chunk.CHUNK_SIZE_Z];
|
|
||||||
boolean empty = true;
|
|
||||||
for (byte y = 0; y < Chunk.CHUNK_SECTION_SIZE; y++) {
|
|
||||||
for (byte x = 0; x < Chunk.CHUNK_SIZE_X; x++) {
|
|
||||||
for (byte z = 0; z < Chunk.CHUNK_SIZE_Z; z++) {
|
|
||||||
final int yPos = (y + Chunk.CHUNK_SECTION_SIZE * section);
|
|
||||||
final int index = ChunkUtils.getBlockIndex(x, yPos, z);
|
|
||||||
final short blockStateId = blocksStateId[index];
|
|
||||||
if (blockStateId != 0)
|
|
||||||
empty = false;
|
|
||||||
|
|
||||||
final int packetIndex = (((y * 16) + x) * 16) + z;
|
|
||||||
blocks[packetIndex] = blockStateId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return empty ? null : blocks;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getId() {
|
public int getId() {
|
||||||
return ServerPacketIdentifier.CHUNK_DATA;
|
return ServerPacketIdentifier.CHUNK_DATA;
|
||||||
|
@ -25,7 +25,7 @@ public class JoinGamePacket implements ServerPacket {
|
|||||||
@Override
|
@Override
|
||||||
public void write(@NotNull BinaryWriter writer) {
|
public void write(@NotNull BinaryWriter writer) {
|
||||||
writer.writeInt(entityId);
|
writer.writeInt(entityId);
|
||||||
writer.writeBoolean(MinecraftServer.isHardcoreLook());
|
writer.writeBoolean(gameMode.isHardcore());
|
||||||
writer.writeByte(gameMode.getId());
|
writer.writeByte(gameMode.getId());
|
||||||
//Previous Gamemode
|
//Previous Gamemode
|
||||||
writer.writeByte(gameMode.getId());
|
writer.writeByte(gameMode.getId());
|
||||||
|
@ -34,8 +34,9 @@ public class PluginMessagePacket implements ServerPacket {
|
|||||||
PluginMessagePacket brandMessage = new PluginMessagePacket();
|
PluginMessagePacket brandMessage = new PluginMessagePacket();
|
||||||
brandMessage.channel = "minecraft:brand";
|
brandMessage.channel = "minecraft:brand";
|
||||||
|
|
||||||
BinaryWriter writer = new BinaryWriter();
|
final String brandName = MinecraftServer.getBrandName();
|
||||||
writer.writeSizedString(MinecraftServer.getBrandName());
|
BinaryWriter writer = new BinaryWriter(4 + brandName.length());
|
||||||
|
writer.writeSizedString(brandName);
|
||||||
|
|
||||||
brandMessage.data = writer.toByteArray();
|
brandMessage.data = writer.toByteArray();
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package net.minestom.server.network.packet.server.play;
|
package net.minestom.server.network.packet.server.play;
|
||||||
|
|
||||||
|
import net.minestom.server.chat.JsonMessage;
|
||||||
import net.minestom.server.network.packet.server.ServerPacket;
|
import net.minestom.server.network.packet.server.ServerPacket;
|
||||||
import net.minestom.server.network.packet.server.ServerPacketIdentifier;
|
import net.minestom.server.network.packet.server.ServerPacketIdentifier;
|
||||||
import net.minestom.server.utils.binary.BinaryWriter;
|
import net.minestom.server.utils.binary.BinaryWriter;
|
||||||
@ -22,7 +23,7 @@ public class TeamsPacket implements ServerPacket {
|
|||||||
/**
|
/**
|
||||||
* The display name for the team
|
* The display name for the team
|
||||||
*/
|
*/
|
||||||
public String teamDisplayName;
|
public JsonMessage teamDisplayName;
|
||||||
/**
|
/**
|
||||||
* The friendly flags to
|
* The friendly flags to
|
||||||
*/
|
*/
|
||||||
@ -42,11 +43,11 @@ public class TeamsPacket implements ServerPacket {
|
|||||||
/**
|
/**
|
||||||
* The prefix of the team
|
* The prefix of the team
|
||||||
*/
|
*/
|
||||||
public String teamPrefix;
|
public JsonMessage teamPrefix;
|
||||||
/**
|
/**
|
||||||
* The suffix of the team
|
* The suffix of the team
|
||||||
*/
|
*/
|
||||||
public String teamSuffix;
|
public JsonMessage teamSuffix;
|
||||||
/**
|
/**
|
||||||
* An array with all entities in the team
|
* An array with all entities in the team
|
||||||
*/
|
*/
|
||||||
@ -65,13 +66,13 @@ public class TeamsPacket implements ServerPacket {
|
|||||||
switch (action) {
|
switch (action) {
|
||||||
case CREATE_TEAM:
|
case CREATE_TEAM:
|
||||||
case UPDATE_TEAM_INFO:
|
case UPDATE_TEAM_INFO:
|
||||||
writer.writeSizedString(this.teamDisplayName);
|
writer.writeSizedString(this.teamDisplayName.toString());
|
||||||
writer.writeByte(this.friendlyFlags);
|
writer.writeByte(this.friendlyFlags);
|
||||||
writer.writeSizedString(this.nameTagVisibility.getIdentifier());
|
writer.writeSizedString(this.nameTagVisibility.getIdentifier());
|
||||||
writer.writeSizedString(this.collisionRule.getIdentifier());
|
writer.writeSizedString(this.collisionRule.getIdentifier());
|
||||||
writer.writeVarInt(this.teamColor);
|
writer.writeVarInt(this.teamColor);
|
||||||
writer.writeSizedString(this.teamPrefix);
|
writer.writeSizedString(this.teamPrefix.toString());
|
||||||
writer.writeSizedString(this.teamSuffix);
|
writer.writeSizedString(this.teamSuffix.toString());
|
||||||
break;
|
break;
|
||||||
case REMOVE_TEAM:
|
case REMOVE_TEAM:
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package net.minestom.server.network.player;
|
package net.minestom.server.network.player;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
import net.minestom.server.MinecraftServer;
|
import net.minestom.server.MinecraftServer;
|
||||||
import net.minestom.server.entity.Player;
|
import net.minestom.server.entity.Player;
|
||||||
import net.minestom.server.entity.fakeplayer.FakePlayer;
|
import net.minestom.server.entity.fakeplayer.FakePlayer;
|
||||||
@ -13,24 +12,11 @@ import java.net.SocketAddress;
|
|||||||
|
|
||||||
public class FakePlayerConnection extends PlayerConnection {
|
public class FakePlayerConnection extends PlayerConnection {
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sendPacket(@NotNull ByteBuf buffer, boolean copy) {
|
|
||||||
throw new UnsupportedOperationException("FakePlayer cannot read Bytebuf");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writePacket(@NotNull ByteBuf buffer, boolean copy) {
|
|
||||||
throw new UnsupportedOperationException("FakePlayer cannot write to Bytebuf");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendPacket(@NotNull ServerPacket serverPacket) {
|
public void sendPacket(@NotNull ServerPacket serverPacket) {
|
||||||
getFakePlayer().getController().consumePacket(serverPacket);
|
if (shouldSendPacket(serverPacket)) {
|
||||||
}
|
getFakePlayer().getController().consumePacket(serverPacket);
|
||||||
|
}
|
||||||
@Override
|
|
||||||
public void flush() {
|
|
||||||
// Does nothing
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
package net.minestom.server.network.player;
|
package net.minestom.server.network.player;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
import io.netty.channel.socket.SocketChannel;
|
import io.netty.channel.socket.SocketChannel;
|
||||||
import lombok.Getter;
|
import net.minestom.server.entity.PlayerSkin;
|
||||||
import lombok.Setter;
|
|
||||||
import net.minestom.server.extras.mojangAuth.Decrypter;
|
import net.minestom.server.extras.mojangAuth.Decrypter;
|
||||||
import net.minestom.server.extras.mojangAuth.Encrypter;
|
import net.minestom.server.extras.mojangAuth.Encrypter;
|
||||||
import net.minestom.server.extras.mojangAuth.MojangCrypt;
|
import net.minestom.server.extras.mojangAuth.MojangCrypt;
|
||||||
@ -19,6 +17,7 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,16 +30,14 @@ public class NettyPlayerConnection extends PlayerConnection {
|
|||||||
private final SocketChannel channel;
|
private final SocketChannel channel;
|
||||||
|
|
||||||
private SocketAddress remoteAddress;
|
private SocketAddress remoteAddress;
|
||||||
@Getter
|
|
||||||
private boolean encrypted = false;
|
private boolean encrypted = false;
|
||||||
@Getter
|
|
||||||
private boolean compressed = false;
|
private boolean compressed = false;
|
||||||
|
|
||||||
//Could be null. Only used for Mojang Auth
|
//Could be null. Only used for Mojang Auth
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private byte[] nonce = new byte[4];
|
private byte[] nonce = new byte[4];
|
||||||
|
|
||||||
|
// Data from client packets
|
||||||
private String loginUsername;
|
private String loginUsername;
|
||||||
private String serverAddress;
|
private String serverAddress;
|
||||||
private int serverPort;
|
private int serverPort;
|
||||||
@ -49,6 +46,10 @@ public class NettyPlayerConnection extends PlayerConnection {
|
|||||||
// cleared once the player enters the play state
|
// cleared once the player enters the play state
|
||||||
private final Map<Integer, String> pluginRequestMap = new ConcurrentHashMap<>();
|
private final Map<Integer, String> pluginRequestMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
// Bungee
|
||||||
|
private UUID bungeeUuid;
|
||||||
|
private PlayerSkin bungeeSkin;
|
||||||
|
|
||||||
public NettyPlayerConnection(@NotNull SocketChannel channel) {
|
public NettyPlayerConnection(@NotNull SocketChannel channel) {
|
||||||
super();
|
super();
|
||||||
this.channel = channel;
|
this.channel = channel;
|
||||||
@ -81,38 +82,22 @@ public class NettyPlayerConnection extends PlayerConnection {
|
|||||||
channel.pipeline().addAfter("framer", "compressor", new PacketCompressor(threshold));
|
channel.pipeline().addAfter("framer", "compressor", new PacketCompressor(threshold));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public void sendPacket(@NotNull ByteBuf buffer, boolean copy) {
|
* Writes a packet to the connection channel.
|
||||||
if (copy) {
|
* <p>
|
||||||
buffer = buffer.copy();
|
* All packets are flushed during {@link net.minestom.server.entity.Player#update(long)}.
|
||||||
buffer.retain();
|
*
|
||||||
channel.writeAndFlush(buffer);
|
* @param serverPacket the packet to write
|
||||||
buffer.release();
|
*/
|
||||||
} else {
|
|
||||||
channel.writeAndFlush(buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writePacket(@NotNull ByteBuf buffer, boolean copy) {
|
|
||||||
if (copy) {
|
|
||||||
buffer = buffer.copy();
|
|
||||||
buffer.retain();
|
|
||||||
channel.write(buffer);
|
|
||||||
buffer.release();
|
|
||||||
} else {
|
|
||||||
channel.write(buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendPacket(@NotNull ServerPacket serverPacket) {
|
public void sendPacket(@NotNull ServerPacket serverPacket) {
|
||||||
channel.writeAndFlush(serverPacket);
|
if (shouldSendPacket(serverPacket)) {
|
||||||
}
|
if (getPlayer() != null) {
|
||||||
|
channel.write(serverPacket); // Flush on player update
|
||||||
@Override
|
} else {
|
||||||
public void flush() {
|
channel.writeAndFlush(serverPacket);
|
||||||
getChannel().flush();
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@ -186,6 +171,24 @@ public class NettyPlayerConnection extends PlayerConnection {
|
|||||||
return serverPort;
|
return serverPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public UUID getBungeeUuid() {
|
||||||
|
return bungeeUuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UNSAFE_setBungeeUuid(UUID bungeeUuid) {
|
||||||
|
this.bungeeUuid = bungeeUuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public PlayerSkin getBungeeSkin() {
|
||||||
|
return bungeeSkin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UNSAFE_setBungeeSkin(PlayerSkin bungeeSkin) {
|
||||||
|
this.bungeeSkin = bungeeSkin;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds an entry to the plugin request map.
|
* Adds an entry to the plugin request map.
|
||||||
* <p>
|
* <p>
|
||||||
@ -235,4 +238,12 @@ public class NettyPlayerConnection extends PlayerConnection {
|
|||||||
this.serverAddress = serverAddress;
|
this.serverAddress = serverAddress;
|
||||||
this.serverPort = serverPort;
|
this.serverPort = serverPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] getNonce() {
|
||||||
|
return nonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNonce(byte[] nonce) {
|
||||||
|
this.nonce = nonce;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
package net.minestom.server.network.player;
|
package net.minestom.server.network.player;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
import lombok.Getter;
|
|
||||||
import net.minestom.server.MinecraftServer;
|
import net.minestom.server.MinecraftServer;
|
||||||
import net.minestom.server.chat.ChatColor;
|
import net.minestom.server.chat.ChatColor;
|
||||||
import net.minestom.server.chat.ColoredText;
|
import net.minestom.server.chat.ColoredText;
|
||||||
import net.minestom.server.entity.Player;
|
import net.minestom.server.entity.Player;
|
||||||
|
import net.minestom.server.listener.manager.PacketConsumer;
|
||||||
|
import net.minestom.server.listener.manager.PacketListenerManager;
|
||||||
|
import net.minestom.server.network.ConnectionManager;
|
||||||
import net.minestom.server.network.ConnectionState;
|
import net.minestom.server.network.ConnectionState;
|
||||||
import net.minestom.server.network.packet.server.ServerPacket;
|
import net.minestom.server.network.packet.server.ServerPacket;
|
||||||
import net.minestom.server.network.packet.server.login.LoginDisconnectPacket;
|
import net.minestom.server.network.packet.server.login.LoginDisconnectPacket;
|
||||||
import net.minestom.server.network.packet.server.play.DisconnectPacket;
|
import net.minestom.server.network.packet.server.play.DisconnectPacket;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
@ -21,6 +23,8 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
*/
|
*/
|
||||||
public abstract class PlayerConnection {
|
public abstract class PlayerConnection {
|
||||||
|
|
||||||
|
protected static final PacketListenerManager PACKET_LISTENER_MANAGER = MinecraftServer.getPacketListenerManager();
|
||||||
|
|
||||||
private Player player;
|
private Player player;
|
||||||
private ConnectionState connectionState;
|
private ConnectionState connectionState;
|
||||||
private boolean online;
|
private boolean online;
|
||||||
@ -29,7 +33,6 @@ public abstract class PlayerConnection {
|
|||||||
private static final ColoredText rateLimitKickMessage = ColoredText.of(ChatColor.RED + "Too Many Packets");
|
private static final ColoredText rateLimitKickMessage = ColoredText.of(ChatColor.RED + "Too Many Packets");
|
||||||
|
|
||||||
//Connection Stats
|
//Connection Stats
|
||||||
@Getter
|
|
||||||
private final AtomicInteger packetCounter = new AtomicInteger(0);
|
private final AtomicInteger packetCounter = new AtomicInteger(0);
|
||||||
private final AtomicInteger lastPacketCounter = new AtomicInteger(0);
|
private final AtomicInteger lastPacketCounter = new AtomicInteger(0);
|
||||||
private short tickCounter = 0;
|
private short tickCounter = 0;
|
||||||
@ -68,33 +71,37 @@ public abstract class PlayerConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public AtomicInteger getPacketCounter() {
|
||||||
* Sends a raw {@link ByteBuf} to the client.
|
return packetCounter;
|
||||||
*
|
}
|
||||||
* @param buffer The buffer to send.
|
|
||||||
* @param copy Should be true unless your only using the ByteBuf once.
|
|
||||||
*/
|
|
||||||
public abstract void sendPacket(@NotNull ByteBuf buffer, boolean copy);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes a raw {@link ByteBuf} to the client.
|
* Returns a printable identifier for this connection, will be the player username
|
||||||
|
* or the connection remote address.
|
||||||
*
|
*
|
||||||
* @param buffer The buffer to send.
|
* @return this connection identifier
|
||||||
* @param copy Should be true unless your only using the ByteBuf once.
|
|
||||||
*/
|
*/
|
||||||
public abstract void writePacket(@NotNull ByteBuf buffer, boolean copy);
|
@NotNull
|
||||||
|
public String getIdentifier() {
|
||||||
|
final Player player = getPlayer();
|
||||||
|
return player != null ?
|
||||||
|
player.getUsername() :
|
||||||
|
getRemoteAddress().toString();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serializes the packet and send it to the client.
|
* Serializes the packet and send it to the client.
|
||||||
|
* <p>
|
||||||
|
* Also responsible for executing {@link ConnectionManager#onPacketSend(PacketConsumer)} consumers.
|
||||||
*
|
*
|
||||||
* @param serverPacket the packet to send
|
* @param serverPacket the packet to send
|
||||||
|
* @see #shouldSendPacket(ServerPacket)
|
||||||
*/
|
*/
|
||||||
public abstract void sendPacket(@NotNull ServerPacket serverPacket);
|
public abstract void sendPacket(@NotNull ServerPacket serverPacket);
|
||||||
|
|
||||||
/**
|
protected boolean shouldSendPacket(@NotNull ServerPacket serverPacket) {
|
||||||
* Flush all waiting packets.
|
return player == null || PACKET_LISTENER_MANAGER.processServerPacket(serverPacket, player);
|
||||||
*/
|
}
|
||||||
public abstract void flush();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the remote address of the client.
|
* Gets the remote address of the client.
|
||||||
@ -112,8 +119,9 @@ public abstract class PlayerConnection {
|
|||||||
/**
|
/**
|
||||||
* Gets the player linked to this connection.
|
* Gets the player linked to this connection.
|
||||||
*
|
*
|
||||||
* @return the player
|
* @return the player, can be null if not initialized yet
|
||||||
*/
|
*/
|
||||||
|
@Nullable
|
||||||
public Player getPlayer() {
|
public Player getPlayer() {
|
||||||
return player;
|
return player;
|
||||||
}
|
}
|
||||||
@ -164,4 +172,12 @@ public abstract class PlayerConnection {
|
|||||||
public int getLastPacketCounter() {
|
public int getLastPacketCounter() {
|
||||||
return lastPacketCounter.get();
|
return lastPacketCounter.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PlayerConnection{" +
|
||||||
|
"connectionState=" + connectionState +
|
||||||
|
", identifier=" + getIdentifier() +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user