mirror of
https://github.com/Minestom/Minestom.git
synced 2025-01-15 20:51:34 +01:00
Merge branch 'master' into test-palette
# Conflicts: # src/main/java/net/minestom/server/instance/DynamicChunk.java
This commit is contained in:
commit
b6bf6a17ba
7
.github/README.md
vendored
7
.github/README.md
vendored
@ -11,7 +11,7 @@ Minestom is an alternative to the popular Minecraft server API called Bukkit.
|
||||
The main difference is that our implementation of the Notchian server does not contain any features by default!
|
||||
However, we have a complete API which allows you to make anything possible with current spigot plugins.
|
||||
|
||||
This is a developer API not meant to be used by the end-users. Replacing Spigot/Paper with this will **not** work since we do not implement the Bukkit API
|
||||
This is a developer API not meant to be used by the end-users. Replacing Spigot/Paper with this will **not** work since we do not implement the Bukkit API.
|
||||
|
||||
# Table of contents
|
||||
- [Install](#install)
|
||||
@ -28,9 +28,11 @@ Minestom is similar to Bukkit in the fact that it is not a standlone program, it
|
||||
It is the base for interfacing between the server and client.
|
||||
Our own expanded version for Vanilla can be found [here](https://github.com/Minestom/VanillaReimplementation).
|
||||
|
||||
This means you need to add Minestom as a dependency, add your code and compile by yourself.
|
||||
|
||||
# Usage
|
||||
An example of how to use the Minestom library is available [here](/src/test/java/demo).
|
||||
Alternatively you can check the official wiki [here](https://github.com/Minestom/Minestom/wiki).
|
||||
Alternatively you can check the official wiki [here](https://wiki.minestom.com/).
|
||||
|
||||
# Why Minestom?
|
||||
Minecraft evolved a lot since its release, most of the servers today do not take advantage of vanilla features and even have to struggle because of them. Our target audience is those who want to make a completely different server compared to default Minecraft gamemode such as survival or creative building.
|
||||
@ -82,6 +84,7 @@ It is a field where Minecraft evolved a lot, inventories are now used a lot as c
|
||||
Commands are the simplest way of communication between clients and server. Since 1.13 Minecraft has incorporated a new library denominated "Brigadier", we then integrated an API meant to use the full potential of args types.
|
||||
|
||||
# Credits
|
||||
* The [contributors](https://github.com/Minestom/Minestom/graphs/contributors) of the project
|
||||
* [The Minecraft Coalition](https://wiki.vg/) and [`#mcdevs`](https://github.com/mcdevs) -
|
||||
protocol and file formats research.
|
||||
* [The Minecraft Wiki](https://minecraft.gamepedia.com/Minecraft_Wiki) for all their useful info
|
||||
|
@ -1,3 +1,3 @@
|
||||
asmVersion=8.0.1
|
||||
mixinVersion=0.8
|
||||
hephaistos_version=v1.1.4
|
||||
hephaistos_version=v1.1.5
|
@ -96,7 +96,6 @@ public abstract class GLFWCapableBuffer {
|
||||
prepareMapColors();
|
||||
}
|
||||
|
||||
// TODO: provide shader that performs the conversion automatically, would be a lot faster
|
||||
/**
|
||||
* Called in render after glFlush to read the pixel buffer contents and convert it to map colors.
|
||||
* Only call if you do not use {@link #render(Runnable)} nor {@link #setupRenderLoop(long, TimeUnit, Runnable)}
|
||||
|
@ -54,6 +54,7 @@ import net.minestom.server.utils.validate.Check;
|
||||
import net.minestom.server.world.Difficulty;
|
||||
import net.minestom.server.world.DimensionTypeManager;
|
||||
import net.minestom.server.world.biomes.BiomeManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -69,13 +70,13 @@ import java.util.Collection;
|
||||
* The server needs to be initialized with {@link #init()} and started with {@link #start(String, int)}.
|
||||
* You should register all of your dimensions, biomes, commands, events, etc... in-between.
|
||||
*/
|
||||
public class MinecraftServer {
|
||||
public final class MinecraftServer {
|
||||
|
||||
@Getter
|
||||
private final static Logger LOGGER = LoggerFactory.getLogger(MinecraftServer.class);
|
||||
|
||||
public static final String VERSION_NAME = "1.16.3";
|
||||
public static final int PROTOCOL_VERSION = 753;
|
||||
public static final String VERSION_NAME = "1.16.4";
|
||||
public static final int PROTOCOL_VERSION = 754;
|
||||
|
||||
// Threads
|
||||
public static final String THREAD_NAME_BENCHMARK = "Ms-Benchmark";
|
||||
@ -227,15 +228,18 @@ public class MinecraftServer {
|
||||
*
|
||||
* @return the server brand name
|
||||
*/
|
||||
@NotNull
|
||||
public static String getBrandName() {
|
||||
return brandName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the server brand name, update the name to all connected players.
|
||||
* Changes the server brand name and send the change to all connected players.
|
||||
*
|
||||
* @param brandName the server brand name
|
||||
* @throws NullPointerException if {@code brandName} is null
|
||||
*/
|
||||
@NotNull
|
||||
public static void setBrandName(String brandName) {
|
||||
Check.notNull(brandName, "The brand name cannot be null");
|
||||
MinecraftServer.brandName = brandName;
|
||||
@ -267,6 +271,7 @@ public class MinecraftServer {
|
||||
*
|
||||
* @return the server difficulty
|
||||
*/
|
||||
@NotNull
|
||||
public static Difficulty getDifficulty() {
|
||||
return difficulty;
|
||||
}
|
||||
@ -276,13 +281,15 @@ public class MinecraftServer {
|
||||
*
|
||||
* @param difficulty the new server difficulty
|
||||
*/
|
||||
public static void setDifficulty(Difficulty difficulty) {
|
||||
@NotNull
|
||||
public static void setDifficulty(@NotNull Difficulty difficulty) {
|
||||
Check.notNull(difficulty, "The server difficulty cannot be null.");
|
||||
MinecraftServer.difficulty = difficulty;
|
||||
|
||||
// The difficulty packet
|
||||
ServerDifficultyPacket serverDifficultyPacket = new ServerDifficultyPacket();
|
||||
serverDifficultyPacket.difficulty = difficulty;
|
||||
serverDifficultyPacket.locked = true; // Can only be modified on singleplayer
|
||||
serverDifficultyPacket.locked = true; // Can only be modified on single-player
|
||||
// Send the packet to all online players
|
||||
PacketWriterUtils.writeAndSend(connectionManager.getOnlinePlayers(), serverDifficultyPacket);
|
||||
}
|
||||
@ -439,10 +446,11 @@ public class MinecraftServer {
|
||||
* Changes the chunk view distance of the server.
|
||||
*
|
||||
* @param chunkViewDistance the new chunk view distance
|
||||
* @throws IllegalStateException if this is called after the server started
|
||||
* @throws IllegalArgumentException if {@code chunkViewDistance} is not between 2 and 32
|
||||
*/
|
||||
public static void setChunkViewDistance(int chunkViewDistance) {
|
||||
Check.argCondition(!MathUtils.isBetween(chunkViewDistance, 2, 32), "The chunk view distance needs to be between 2 and 32");
|
||||
Check.argCondition(!MathUtils.isBetween(chunkViewDistance, 2, 32),
|
||||
"The chunk view distance must be between 2 and 32");
|
||||
MinecraftServer.chunkViewDistance = chunkViewDistance;
|
||||
if (started) {
|
||||
UpdateViewDistancePacket updateViewDistancePacket = new UpdateViewDistancePacket();
|
||||
@ -472,15 +480,22 @@ public class MinecraftServer {
|
||||
|
||||
/**
|
||||
* Changes the entity view distance of the server.
|
||||
* <p>
|
||||
* WARNING: this need to be called before {@link #start(String, int, ResponseDataConsumer)}.
|
||||
*
|
||||
* @param entityViewDistance the new entity view distance
|
||||
* @throws IllegalStateException if this is called after the server started
|
||||
* @throws IllegalArgumentException if {@code entityViewDistance} is not between 0 and 32
|
||||
*/
|
||||
public static void setEntityViewDistance(int entityViewDistance) {
|
||||
Check.stateCondition(started, "The entity view distance cannot be changed after the server has been started.");
|
||||
Check.argCondition(!MathUtils.isBetween(entityViewDistance, 0, 32),
|
||||
"The entity view distance must be between 0 and 32");
|
||||
MinecraftServer.entityViewDistance = entityViewDistance;
|
||||
if (started) {
|
||||
connectionManager.getOnlinePlayers().forEach(player -> {
|
||||
final Chunk playerChunk = player.getChunk();
|
||||
if (playerChunk != null) {
|
||||
player.refreshVisibleEntities(playerChunk);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -587,12 +602,15 @@ public class MinecraftServer {
|
||||
|
||||
/**
|
||||
* Starts the server.
|
||||
* <p>
|
||||
* It should be called after {@link #init()} and probably your own initialization code.
|
||||
*
|
||||
* @param address the server address
|
||||
* @param port the server port
|
||||
* @param responseDataConsumer the response data consumer, can be null
|
||||
* @throws IllegalStateException if called before {@link #init()} or if the server is already running
|
||||
*/
|
||||
public void start(String address, int port, ResponseDataConsumer responseDataConsumer) {
|
||||
public void start(@NotNull String address, int port, @Nullable ResponseDataConsumer responseDataConsumer) {
|
||||
Check.stateCondition(!initialized, "#start can only be called after #init");
|
||||
Check.stateCondition(started, "The server is already started");
|
||||
|
||||
@ -607,7 +625,8 @@ public class MinecraftServer {
|
||||
extensionManager.getExtensions().forEach(Extension::initialize);
|
||||
extensionManager.getExtensions().forEach(Extension::postInitialize);
|
||||
|
||||
LOGGER.info("Extensions loaded in " + (t1 + System.nanoTime()) / 1_000_000D + "ms");
|
||||
final double loadTime = MathUtils.round((t1 + System.nanoTime()) / 1_000_000D, 2);
|
||||
LOGGER.info("Extensions loaded in " + loadTime + "ms");
|
||||
LOGGER.info("Minestom server started successfully.");
|
||||
|
||||
MinecraftServer.started = true;
|
||||
@ -618,8 +637,9 @@ public class MinecraftServer {
|
||||
*
|
||||
* @param address the server address
|
||||
* @param port the server port
|
||||
* @see #start(String, int, ResponseDataConsumer)
|
||||
*/
|
||||
public void start(String address, int port) {
|
||||
public void start(@NotNull String address, int port) {
|
||||
start(address, port, null);
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,7 @@ public final class UpdateManager {
|
||||
private final ConcurrentLinkedQueue<DoubleConsumer> tickEndCallbacks = new ConcurrentLinkedQueue<>();
|
||||
|
||||
{
|
||||
// DEFAULT THREAD PROVIDER
|
||||
//threadProvider = new PerInstanceThreadProvider();
|
||||
threadProvider = new PerGroupChunkProvider();
|
||||
}
|
||||
|
@ -6,6 +6,8 @@ import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.item.Material;
|
||||
import net.minestom.server.network.packet.server.play.AdvancementsPacket;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@ -39,8 +41,8 @@ public class Advancement {
|
||||
// Packet
|
||||
private AdvancementsPacket.Criteria criteria;
|
||||
|
||||
public Advancement(ColoredText title, ColoredText description,
|
||||
ItemStack icon, FrameType frameType,
|
||||
public Advancement(@NotNull ColoredText title, ColoredText description,
|
||||
@NotNull ItemStack icon, @NotNull FrameType frameType,
|
||||
float x, float y) {
|
||||
this.title = title;
|
||||
this.description = description;
|
||||
@ -50,8 +52,8 @@ public class Advancement {
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public Advancement(ColoredText title, ColoredText description,
|
||||
Material icon, FrameType frameType,
|
||||
public Advancement(@NotNull ColoredText title, @NotNull ColoredText description,
|
||||
@NotNull Material icon, @NotNull FrameType frameType,
|
||||
float x, float y) {
|
||||
this(title, description, new ItemStack(icon, (byte) 1), frameType, x, y);
|
||||
}
|
||||
@ -80,13 +82,14 @@ public class Advancement {
|
||||
/**
|
||||
* Gets the advancement tab linked to this advancement.
|
||||
*
|
||||
* @return the {@link AdvancementTab} linked to this advancement
|
||||
* @return the {@link AdvancementTab} linked to this advancement, null if not linked to anything yet
|
||||
*/
|
||||
@Nullable
|
||||
public AdvancementTab getTab() {
|
||||
return tab;
|
||||
}
|
||||
|
||||
protected void setTab(AdvancementTab tab) {
|
||||
protected void setTab(@NotNull AdvancementTab tab) {
|
||||
this.tab = tab;
|
||||
}
|
||||
|
||||
@ -95,6 +98,7 @@ public class Advancement {
|
||||
*
|
||||
* @return the advancement title
|
||||
*/
|
||||
@NotNull
|
||||
public ColoredText getTitle() {
|
||||
return title;
|
||||
}
|
||||
@ -104,7 +108,7 @@ public class Advancement {
|
||||
*
|
||||
* @param title the new title
|
||||
*/
|
||||
public void setTitle(ColoredText title) {
|
||||
public void setTitle(@NotNull ColoredText title) {
|
||||
this.title = title;
|
||||
update();
|
||||
}
|
||||
@ -114,6 +118,7 @@ public class Advancement {
|
||||
*
|
||||
* @return the description title
|
||||
*/
|
||||
@NotNull
|
||||
public ColoredText getDescription() {
|
||||
return description;
|
||||
}
|
||||
@ -123,7 +128,7 @@ public class Advancement {
|
||||
*
|
||||
* @param description the new description
|
||||
*/
|
||||
public void setDescription(ColoredText description) {
|
||||
public void setDescription(@NotNull ColoredText description) {
|
||||
this.description = description;
|
||||
update();
|
||||
}
|
||||
@ -133,6 +138,7 @@ public class Advancement {
|
||||
*
|
||||
* @return the advancement icon
|
||||
*/
|
||||
@NotNull
|
||||
public ItemStack getIcon() {
|
||||
return icon;
|
||||
}
|
||||
@ -142,7 +148,7 @@ public class Advancement {
|
||||
*
|
||||
* @param icon the new advancement icon
|
||||
*/
|
||||
public void setIcon(ItemStack icon) {
|
||||
public void setIcon(@NotNull ItemStack icon) {
|
||||
this.icon = icon;
|
||||
update();
|
||||
}
|
||||
@ -182,6 +188,7 @@ public class Advancement {
|
||||
*
|
||||
* @return this advancement frame type
|
||||
*/
|
||||
@NotNull
|
||||
public FrameType getFrameType() {
|
||||
return frameType;
|
||||
}
|
||||
@ -271,6 +278,7 @@ public class Advancement {
|
||||
*
|
||||
* @return the advancement parent, null for {@link AdvancementRoot}
|
||||
*/
|
||||
@Nullable
|
||||
protected Advancement getParent() {
|
||||
return parent;
|
||||
}
|
||||
@ -279,6 +287,7 @@ public class Advancement {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected AdvancementsPacket.ProgressMapping toProgressMapping() {
|
||||
AdvancementsPacket.ProgressMapping progressMapping = new AdvancementsPacket.ProgressMapping();
|
||||
{
|
||||
@ -291,6 +300,7 @@ public class Advancement {
|
||||
return progressMapping;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected AdvancementsPacket.DisplayData toDisplayData() {
|
||||
AdvancementsPacket.DisplayData displayData = new AdvancementsPacket.DisplayData();
|
||||
displayData.x = x;
|
||||
@ -311,6 +321,7 @@ public class Advancement {
|
||||
*
|
||||
* @return the mapping of this advancement
|
||||
*/
|
||||
@NotNull
|
||||
protected AdvancementsPacket.AdvancementMapping toMapping() {
|
||||
AdvancementsPacket.AdvancementMapping mapping = new AdvancementsPacket.AdvancementMapping();
|
||||
{
|
||||
|
@ -1,6 +1,8 @@
|
||||
package net.minestom.server.advancements;
|
||||
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
@ -13,6 +15,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
*/
|
||||
public class AdvancementManager {
|
||||
|
||||
// root identifier = its advancement tab
|
||||
private final Map<String, AdvancementTab> advancementTabMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
@ -23,7 +26,8 @@ public class AdvancementManager {
|
||||
* @return the newly created {@link AdvancementTab}
|
||||
* @throws IllegalStateException if a tab with the identifier {@code rootIdentifier} already exists
|
||||
*/
|
||||
public AdvancementTab createTab(String rootIdentifier, AdvancementRoot root) {
|
||||
@NotNull
|
||||
public AdvancementTab createTab(@NotNull String rootIdentifier, @NotNull AdvancementRoot root) {
|
||||
Check.stateCondition(advancementTabMap.containsKey(rootIdentifier),
|
||||
"A tab with the identifier '" + rootIdentifier + "' already exists");
|
||||
final AdvancementTab advancementTab = new AdvancementTab(rootIdentifier, root);
|
||||
@ -37,7 +41,8 @@ public class AdvancementManager {
|
||||
* @param rootIdentifier the root identifier of the tab
|
||||
* @return the {@link AdvancementTab} associated with the identifier, null if not any
|
||||
*/
|
||||
public AdvancementTab getTab(String rootIdentifier) {
|
||||
@Nullable
|
||||
public AdvancementTab getTab(@NotNull String rootIdentifier) {
|
||||
return advancementTabMap.get(rootIdentifier);
|
||||
}
|
||||
|
||||
@ -46,6 +51,7 @@ public class AdvancementManager {
|
||||
*
|
||||
* @return the collection containing all created {@link AdvancementTab}
|
||||
*/
|
||||
@NotNull
|
||||
public Collection<AdvancementTab> getTabs() {
|
||||
return advancementTabMap.values();
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ package net.minestom.server.advancements;
|
||||
import net.minestom.server.chat.ColoredText;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.item.Material;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Represents an {@link Advancement} which is the root of an {@link AdvancementTab}.
|
||||
@ -12,18 +14,18 @@ import net.minestom.server.item.Material;
|
||||
*/
|
||||
public class AdvancementRoot extends Advancement {
|
||||
|
||||
public AdvancementRoot(ColoredText title, ColoredText description,
|
||||
ItemStack icon, FrameType frameType,
|
||||
public AdvancementRoot(@NotNull ColoredText title, @NotNull ColoredText description,
|
||||
@NotNull ItemStack icon, @NotNull FrameType frameType,
|
||||
float x, float y,
|
||||
String background) {
|
||||
@Nullable String background) {
|
||||
super(title, description, icon, frameType, x, y);
|
||||
setBackground(background);
|
||||
}
|
||||
|
||||
public AdvancementRoot(ColoredText title, ColoredText description,
|
||||
Material icon, FrameType frameType,
|
||||
public AdvancementRoot(@NotNull ColoredText title, @NotNull ColoredText description,
|
||||
@NotNull Material icon, FrameType frameType,
|
||||
float x, float y,
|
||||
String background) {
|
||||
@Nullable String background) {
|
||||
super(title, description, icon, frameType, x, y);
|
||||
setBackground(background);
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import net.minestom.server.chat.ChatColor;
|
||||
import net.minestom.server.utils.MathUtils;
|
||||
import net.minestom.server.utils.time.UpdateOption;
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.ThreadInfo;
|
||||
@ -24,6 +25,8 @@ import static net.minestom.server.MinecraftServer.*;
|
||||
* <p>
|
||||
* Needs to be enabled with {@link #enable(UpdateOption)}. Memory can then be accessed with {@link #getUsedMemory()}
|
||||
* and the CPUs usage with {@link #getResultMap()} or {@link #getCpuMonitoringMessage()}.
|
||||
* <p>
|
||||
* Be aware that this is not the most accurate method, you should use a proper java profiler depending on your needs.
|
||||
*/
|
||||
public final class BenchmarkManager {
|
||||
|
||||
@ -53,7 +56,7 @@ public final class BenchmarkManager {
|
||||
|
||||
private long time;
|
||||
|
||||
public void enable(UpdateOption updateOption) {
|
||||
public void enable(@NotNull UpdateOption updateOption) {
|
||||
Check.stateCondition(enabled, "A benchmark is already running, please disable it first.");
|
||||
|
||||
time = updateOption.getTimeUnit().toMilliseconds(updateOption.getValue());
|
||||
@ -84,7 +87,7 @@ public final class BenchmarkManager {
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
public void addThreadMonitor(String threadName) {
|
||||
public void addThreadMonitor(@NotNull String threadName) {
|
||||
THREADS.add(threadName);
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.minestom.server.bossbar;
|
||||
|
||||
/**
|
||||
* Represents the displayed color of a {@link BossBar}
|
||||
* Represents the displayed color of a {@link BossBar}.
|
||||
*/
|
||||
public enum BarColor {
|
||||
PINK,
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.minestom.server.bossbar;
|
||||
|
||||
/**
|
||||
* Used to define the number of segments on a {@link BossBar}
|
||||
* Used to define the number of segments on a {@link BossBar}.
|
||||
*/
|
||||
public enum BarDivision {
|
||||
SOLID,
|
||||
|
@ -40,7 +40,7 @@ public class BossBar implements Viewable {
|
||||
* @param color the boss bar color
|
||||
* @param division the boss bar division
|
||||
*/
|
||||
public BossBar(ColoredText title, @NotNull BarColor color, @NotNull BarDivision division) {
|
||||
public BossBar(@NotNull ColoredText title, @NotNull BarColor color, @NotNull BarDivision division) {
|
||||
this.title = title;
|
||||
this.color = color;
|
||||
this.division = division;
|
||||
@ -96,6 +96,7 @@ public class BossBar implements Viewable {
|
||||
*
|
||||
* @return the current title of the bossbar
|
||||
*/
|
||||
@NotNull
|
||||
public ColoredText getTitle() {
|
||||
return title;
|
||||
}
|
||||
@ -105,7 +106,7 @@ public class BossBar implements Viewable {
|
||||
*
|
||||
* @param title the new title of the bossbar
|
||||
*/
|
||||
public void setTitle(ColoredText title) {
|
||||
public void setTitle(@NotNull ColoredText title) {
|
||||
this.title = title;
|
||||
updateTitle();
|
||||
}
|
||||
|
@ -242,6 +242,14 @@ public class ChatColor {
|
||||
return codeName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the color id, only present if this color has been retrieved from {@link ChatColor} constants.
|
||||
* <p>
|
||||
* Should only be used for some special packets which require it.
|
||||
*
|
||||
* @return the color id
|
||||
* @throws IllegalStateException if the color is not from the class constants
|
||||
*/
|
||||
public int getId() {
|
||||
Check.stateCondition(id == -1, "Please use one of the ChatColor constant instead");
|
||||
return id;
|
||||
@ -262,20 +270,10 @@ public class ChatColor {
|
||||
// color or special code (white/red/reset/bold/etc...)
|
||||
code = codeName;
|
||||
} else {
|
||||
// RGB color
|
||||
String redH = Integer.toHexString(red);
|
||||
if (redH.length() == 1)
|
||||
redH = "0" + redH;
|
||||
// RGB color (special code not set)
|
||||
final int color = (red & 0xFF) << 16 | (green & 0xFF) << 8 | blue & 0xFF;
|
||||
|
||||
String greenH = Integer.toHexString(green);
|
||||
if (greenH.length() == 1)
|
||||
greenH = "0" + greenH;
|
||||
|
||||
String blueH = Integer.toHexString(blue);
|
||||
if (blueH.length() == 1)
|
||||
blueH = "0" + blueH;
|
||||
|
||||
code = redH + greenH + blueH;
|
||||
code = Integer.toHexString(color);
|
||||
}
|
||||
|
||||
return header + code + footer;
|
||||
|
@ -13,8 +13,8 @@ import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
||||
public class ChatHoverEvent {
|
||||
|
||||
private final String action;
|
||||
private String value;
|
||||
private JsonObject valueObject;
|
||||
private final String value;
|
||||
private final JsonObject valueObject;
|
||||
private final boolean isJson;
|
||||
|
||||
private ChatHoverEvent(@NotNull String action, @NotNull String value) {
|
||||
|
@ -58,7 +58,7 @@ public abstract class JsonMessage {
|
||||
|
||||
public static class RawJsonMessage extends JsonMessage {
|
||||
|
||||
private JsonObject jsonObject;
|
||||
private final JsonObject jsonObject;
|
||||
|
||||
public RawJsonMessage(@NotNull JsonObject jsonObject) {
|
||||
this.jsonObject = jsonObject;
|
||||
|
@ -61,28 +61,25 @@ public class BoundingBox {
|
||||
|
||||
final float offsetX = 1;
|
||||
final float x = blockPosition.getX();
|
||||
final float minX = x;
|
||||
final float maxX = x + offsetX;
|
||||
|
||||
final boolean checkX = getMinX() < maxX && getMaxX() > minX;
|
||||
final boolean checkX = getMinX() < maxX && getMaxX() > x;
|
||||
if (!checkX)
|
||||
return false;
|
||||
|
||||
final float y = blockPosition.getY();
|
||||
final float minY = y;
|
||||
final float maxY = y + 0.99999f;
|
||||
|
||||
final boolean checkY = getMinY() < maxY && getMaxY() > minY;
|
||||
final boolean checkY = getMinY() < maxY && getMaxY() > y;
|
||||
if (!checkY)
|
||||
return false;
|
||||
|
||||
final float offsetZ = 1;
|
||||
final float z = blockPosition.getZ();
|
||||
final float minZ = z;
|
||||
final float maxZ = z + offsetZ;
|
||||
|
||||
final boolean checkZ = getMinZ() < maxZ && getMaxZ() > minZ;
|
||||
return checkZ;
|
||||
// Z check
|
||||
return getMinZ() < maxZ && getMaxZ() > z;
|
||||
}
|
||||
|
||||
public boolean intersect(float x, float y, float z) {
|
||||
|
@ -105,7 +105,7 @@ public class CollisionUtils {
|
||||
if (!collisionFound) {
|
||||
Vector direction = new Vector();
|
||||
direction.copy(axis);
|
||||
collisionFound |= !stepOnce(instance, direction, remainingLength, cornersCopy, cornerPositions);
|
||||
collisionFound = !stepOnce(instance, direction, remainingLength, cornersCopy, cornerPositions);
|
||||
}
|
||||
|
||||
// find the corner which moved the least
|
||||
|
@ -15,11 +15,15 @@ import net.minestom.server.command.builder.arguments.number.ArgumentDouble;
|
||||
import net.minestom.server.command.builder.arguments.number.ArgumentFloat;
|
||||
import net.minestom.server.command.builder.arguments.number.ArgumentInteger;
|
||||
import net.minestom.server.command.builder.arguments.number.ArgumentNumber;
|
||||
import net.minestom.server.command.builder.arguments.relative.ArgumentRelativeBlockPosition;
|
||||
import net.minestom.server.command.builder.arguments.relative.ArgumentRelativeVec2;
|
||||
import net.minestom.server.command.builder.arguments.relative.ArgumentRelativeVec3;
|
||||
import net.minestom.server.command.builder.condition.CommandCondition;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.event.player.PlayerCommandEvent;
|
||||
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
|
||||
import net.minestom.server.utils.ArrayUtils;
|
||||
import net.minestom.server.utils.callback.CommandCallback;
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -37,13 +41,15 @@ public final class CommandManager {
|
||||
|
||||
public static final String COMMAND_PREFIX = "/";
|
||||
|
||||
private boolean running;
|
||||
private volatile boolean running;
|
||||
|
||||
private final ConsoleSender consoleSender = new ConsoleSender();
|
||||
|
||||
private final CommandDispatcher dispatcher = new CommandDispatcher();
|
||||
private final Map<String, CommandProcessor> commandProcessorMap = new HashMap<>();
|
||||
|
||||
private CommandCallback unknownCommandCallback;
|
||||
|
||||
public CommandManager() {
|
||||
running = true;
|
||||
// Setup console thread
|
||||
@ -58,6 +64,7 @@ public final class CommandManager {
|
||||
execute(consoleSender, command);
|
||||
}
|
||||
}
|
||||
scanner.close();
|
||||
}, "ConsoleCommand-Thread");
|
||||
consoleThread.setDaemon(true);
|
||||
consoleThread.start();
|
||||
@ -153,8 +160,12 @@ public final class CommandManager {
|
||||
final String[] splitCommand = command.split(" ");
|
||||
final String commandName = splitCommand[0];
|
||||
final CommandProcessor commandProcessor = commandProcessorMap.get(commandName.toLowerCase());
|
||||
if (commandProcessor == null)
|
||||
if (commandProcessor == null) {
|
||||
if (unknownCommandCallback != null) {
|
||||
this.unknownCommandCallback.apply(sender, command);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Execute the legacy-command
|
||||
final String[] args = command.substring(command.indexOf(" ") + 1).split(" ");
|
||||
@ -164,6 +175,26 @@ public final class CommandManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the callback executed once an unknown command is run.
|
||||
*
|
||||
* @return the unknown command callback, null if not any
|
||||
*/
|
||||
@Nullable
|
||||
public CommandCallback getUnknownCommandCallback() {
|
||||
return unknownCommandCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the callback executed once an unknown command is run.
|
||||
*
|
||||
* @param unknownCommandCallback the new unknown command callback,
|
||||
* setting it to null mean that nothing will be executed
|
||||
*/
|
||||
public void setUnknownCommandCallback(@Nullable CommandCallback unknownCommandCallback) {
|
||||
this.unknownCommandCallback = unknownCommandCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link ConsoleSender} (which is used as a {@link CommandSender}).
|
||||
*
|
||||
@ -177,7 +208,7 @@ public final class CommandManager {
|
||||
/**
|
||||
* Gets the {@link DeclareCommandsPacket} for a specific player.
|
||||
* <p>
|
||||
* Can be used to update the {@link Player} auto-completion list.
|
||||
* Can be used to update a player auto-completion list.
|
||||
*
|
||||
* @param player the player to get the commands packet
|
||||
* @return the {@link DeclareCommandsPacket} for {@code player}
|
||||
@ -207,7 +238,7 @@ public final class CommandManager {
|
||||
final CommandCondition commandCondition = command.getCondition();
|
||||
if (commandCondition != null) {
|
||||
// Do not show command if return false
|
||||
if (!commandCondition.apply(player)) {
|
||||
if (!commandCondition.canUse(player, null)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@ -220,7 +251,7 @@ public final class CommandManager {
|
||||
names.add(command.getName());
|
||||
names.addAll(Arrays.asList(command.getAliases()));
|
||||
for (String name : names) {
|
||||
createCommand(nodes, cmdChildren, name, syntaxes, rootChildren);
|
||||
createCommand(player, nodes, cmdChildren, name, syntaxes, rootChildren);
|
||||
}
|
||||
|
||||
}
|
||||
@ -284,13 +315,15 @@ public final class CommandManager {
|
||||
/**
|
||||
* Adds the command's syntaxes to the nodes list.
|
||||
*
|
||||
* @param sender the potential sender of the command
|
||||
* @param nodes the nodes of the packet
|
||||
* @param cmdChildren the main root of this command
|
||||
* @param name the name of the command (or the alias)
|
||||
* @param syntaxes the syntaxes of the command
|
||||
* @param rootChildren the children of the main node (all commands name)
|
||||
*/
|
||||
private void createCommand(@NotNull List<DeclareCommandsPacket.Node> nodes,
|
||||
private void createCommand(@NotNull CommandSender sender,
|
||||
@NotNull List<DeclareCommandsPacket.Node> nodes,
|
||||
@NotNull IntList cmdChildren,
|
||||
@NotNull String name,
|
||||
@NotNull Collection<CommandSyntax> syntaxes,
|
||||
@ -307,6 +340,13 @@ public final class CommandManager {
|
||||
Map<Argument, List<DeclareCommandsPacket.Node>> storedArgumentsNodes = new HashMap<>();
|
||||
|
||||
for (CommandSyntax syntax : syntaxes) {
|
||||
final CommandCondition commandCondition = syntax.getCommandCondition();
|
||||
if (commandCondition != null && !commandCondition.canUse(sender, null)) {
|
||||
// Sender does not have the right to use this syntax, ignore it
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// Represent the last nodes computed in the last iteration
|
||||
List<DeclareCommandsPacket.Node> lastNodes = null;
|
||||
|
||||
@ -404,9 +444,8 @@ public final class CommandManager {
|
||||
List<DeclareCommandsPacket.Node> nodes = new ArrayList<>();
|
||||
|
||||
// You can uncomment this to test any brigadier parser on the client
|
||||
/*DeclareCommandsPacket.Node testNode = simpleArgumentNode(nodes, argument, executable);
|
||||
testNode.parser = "minecraft:entity";
|
||||
testNode.properties = packetWriter -> packetWriter.writeByte((byte) 0x0);
|
||||
/*DeclareCommandsPacket.Node testNode = simpleArgumentNode(nodes, argument, executable, false);
|
||||
testNode.parser = "minecraft:vec3";
|
||||
|
||||
if (true) {
|
||||
return nodes;
|
||||
@ -559,6 +598,15 @@ public final class CommandManager {
|
||||
} else if (argument instanceof ArgumentNbtTag) {
|
||||
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false);
|
||||
argumentNode.parser = "minecraft:nbt_tag";
|
||||
} else if (argument instanceof ArgumentRelativeBlockPosition) {
|
||||
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false);
|
||||
argumentNode.parser = "minecraft:block_pos";
|
||||
} else if (argument instanceof ArgumentRelativeVec3) {
|
||||
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false);
|
||||
argumentNode.parser = "minecraft:vec3";
|
||||
} else if (argument instanceof ArgumentRelativeVec2) {
|
||||
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(nodes, argument, executable, false);
|
||||
argumentNode.parser = "minecraft:vec2";
|
||||
}
|
||||
|
||||
return nodes;
|
||||
|
@ -3,6 +3,7 @@ package net.minestom.server.command;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.permission.Permission;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@ -63,19 +64,23 @@ public interface CommandSender {
|
||||
* 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)}
|
||||
* @return true if the sender has the permission and validate {@link Permission#isValidFor(CommandSender, Object)}
|
||||
*/
|
||||
default boolean hasPermission(@NotNull Permission p) {
|
||||
return getAllPermissions().contains(p) && p.isValidFor(this);
|
||||
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)} on all permissions that are an instance of {@code permissionClass}.
|
||||
* 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)}
|
||||
* @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) {
|
||||
@ -84,7 +89,21 @@ public interface CommandSender {
|
||||
for (Permission p : getAllPermissions()) {
|
||||
if (permissionClass.isInstance(p)) {
|
||||
foundPerm = true;
|
||||
result &= p.isValidFor(this);
|
||||
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)
|
||||
|
@ -7,6 +7,8 @@ import net.minestom.server.item.Enchantment;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.particle.Particle;
|
||||
import net.minestom.server.potion.PotionEffect;
|
||||
import net.minestom.server.utils.location.RelativeBlockPosition;
|
||||
import net.minestom.server.utils.location.RelativeVec;
|
||||
import net.minestom.server.utils.math.FloatRange;
|
||||
import net.minestom.server.utils.math.IntRange;
|
||||
import net.minestom.server.utils.time.UpdateOption;
|
||||
@ -126,6 +128,16 @@ public final class Arguments {
|
||||
return (NBT) getObject(id);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public RelativeBlockPosition getRelativeBlockPosition(@NotNull String id) {
|
||||
return (RelativeBlockPosition) getObject(id);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public RelativeVec getRelativeVector(@NotNull String id) {
|
||||
return (RelativeVec) getObject(id);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Object getObject(@NotNull String id) {
|
||||
return args.computeIfAbsent(id, s -> {
|
||||
|
@ -104,16 +104,31 @@ public class Command {
|
||||
/**
|
||||
* Adds a new syntax in the command.
|
||||
* <p>
|
||||
* A syntax is simply a list of arguments
|
||||
* A syntax is simply a list of arguments.
|
||||
*
|
||||
* @param commandCondition the condition to use the syntax
|
||||
* @param executor the executor to call when the syntax is successfully received
|
||||
* @param args all the arguments of the syntax
|
||||
* @return the created {@link CommandSyntax}
|
||||
*/
|
||||
public CommandSyntax addSyntax(@Nullable CommandCondition commandCondition,
|
||||
@NotNull CommandExecutor executor,
|
||||
@NotNull Argument<?>... args) {
|
||||
final CommandSyntax syntax = new CommandSyntax(commandCondition, executor, args);
|
||||
this.syntaxes.add(syntax);
|
||||
return syntax;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new syntax in the command without any condition.
|
||||
*
|
||||
* @param executor the executor to call when the syntax is successfully received
|
||||
* @param args all the arguments of the syntax
|
||||
* @return the created {@link CommandSyntax}
|
||||
* @see #addSyntax(CommandCondition, CommandExecutor, Argument[])
|
||||
*/
|
||||
public CommandSyntax addSyntax(@NotNull CommandExecutor executor, @NotNull Argument<?>... args) {
|
||||
final CommandSyntax syntax = new CommandSyntax(executor, args);
|
||||
this.syntaxes.add(syntax);
|
||||
return syntax;
|
||||
return addSyntax(null, executor, args);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -148,9 +163,10 @@ public class Command {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default {@link CommandExecutor} (which is called when there is no argument).
|
||||
* Sets the default {@link CommandExecutor}.
|
||||
*
|
||||
* @param executor the new default executor, null to remove it
|
||||
* @see #getDefaultExecutor()
|
||||
*/
|
||||
public void setDefaultExecutor(@Nullable CommandExecutor executor) {
|
||||
this.defaultExecutor = executor;
|
||||
|
@ -110,6 +110,7 @@ public class CommandDispatcher {
|
||||
final String[] argsValues = new String[arguments.length];
|
||||
|
||||
boolean syntaxCorrect = true;
|
||||
// The current index in the raw command string arguments
|
||||
int argIndex = 0;
|
||||
|
||||
boolean useRemaining = false;
|
||||
@ -126,20 +127,24 @@ public class CommandDispatcher {
|
||||
StringBuilder argValue = new StringBuilder();
|
||||
|
||||
if (useRemaining) {
|
||||
// Argument is supposed to take the rest of the command input
|
||||
for (int i = argIndex; i < args.length; i++) {
|
||||
final String arg = args[i];
|
||||
if (argValue.length() > 0)
|
||||
argValue.append(" ");
|
||||
argValue.append(arg);
|
||||
}
|
||||
final boolean hasArgs = args.length > argIndex;
|
||||
// Verify if there is any string part available
|
||||
if (hasArgs) {
|
||||
// Argument is supposed to take the rest of the command input
|
||||
for (int i = argIndex; i < args.length; i++) {
|
||||
final String arg = args[i];
|
||||
if (argValue.length() > 0)
|
||||
argValue.append(" ");
|
||||
argValue.append(arg);
|
||||
}
|
||||
|
||||
final String argValueString = argValue.toString();
|
||||
final String argValueString = argValue.toString();
|
||||
|
||||
correctionResult = argument.getCorrectionResult(argValueString);
|
||||
if (correctionResult == Argument.SUCCESS) {
|
||||
correct = true;
|
||||
argsValues[argIndex] = argValueString;
|
||||
correctionResult = argument.getCorrectionResult(argValueString);
|
||||
if (correctionResult == Argument.SUCCESS) {
|
||||
correct = true;
|
||||
argsValues[argIndex] = argValueString;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Argument is either single-word or can accept optional delimited space(s)
|
||||
@ -193,6 +198,7 @@ public class CommandDispatcher {
|
||||
final CommandSyntax finalSyntax = findMostCorrectSyntax(validSyntaxes, syntaxesValues, executorArgs);
|
||||
if (finalSyntax != null) {
|
||||
// A fully correct syntax has been found, use it
|
||||
result.syntax = finalSyntax;
|
||||
result.executor = finalSyntax.getExecutor();
|
||||
result.arguments = executorArgs;
|
||||
return result;
|
||||
@ -328,6 +334,8 @@ public class CommandDispatcher {
|
||||
private Command command;
|
||||
|
||||
// Command Executor
|
||||
private CommandSyntax syntax;
|
||||
|
||||
private CommandExecutor executor;
|
||||
private Arguments arguments;
|
||||
|
||||
@ -348,17 +356,27 @@ public class CommandDispatcher {
|
||||
public void execute(@NotNull CommandSender source, @NotNull String commandString) {
|
||||
// Global listener
|
||||
command.globalListener(source, arguments, commandString);
|
||||
// Condition check
|
||||
// Command condition check
|
||||
final CommandCondition condition = command.getCondition();
|
||||
if (condition != null) {
|
||||
final boolean result = condition.apply(source);
|
||||
final boolean result = condition.canUse(source, commandString);
|
||||
if (!result)
|
||||
return;
|
||||
}
|
||||
// Condition is respected
|
||||
if (executor != null) {
|
||||
// An executor has been found
|
||||
executor.apply(source, arguments);
|
||||
|
||||
if (syntax != null) {
|
||||
// The executor is from a syntax
|
||||
final CommandCondition commandCondition = syntax.getCommandCondition();
|
||||
if (commandCondition == null || commandCondition.canUse(source, commandString)) {
|
||||
executor.apply(source, arguments);
|
||||
}
|
||||
} else {
|
||||
// The executor is probably the default one
|
||||
executor.apply(source, arguments);
|
||||
}
|
||||
} else if (callback != null) {
|
||||
// No syntax has been validated but the faulty argument with a callback has been found
|
||||
// Execute the faulty argument callback
|
||||
|
@ -1,7 +1,10 @@
|
||||
package net.minestom.server.command.builder;
|
||||
|
||||
import net.minestom.server.command.builder.arguments.Argument;
|
||||
import net.minestom.server.command.builder.condition.CommandCondition;
|
||||
import net.minestom.server.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a syntax in {@link Command}
|
||||
@ -9,22 +12,39 @@ import org.jetbrains.annotations.NotNull;
|
||||
*/
|
||||
public class CommandSyntax {
|
||||
|
||||
private final Argument<?>[] args;
|
||||
private CommandCondition commandCondition;
|
||||
private CommandExecutor executor;
|
||||
private final Argument<?>[] args;
|
||||
|
||||
protected CommandSyntax(@NotNull CommandExecutor commandExecutor, @NotNull Argument<?>... args) {
|
||||
protected CommandSyntax(@Nullable CommandCondition commandCondition,
|
||||
@NotNull CommandExecutor commandExecutor,
|
||||
@NotNull Argument<?>... args) {
|
||||
this.commandCondition = commandCondition;
|
||||
this.executor = commandExecutor;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the required {@link Argument} for this syntax.
|
||||
* Gets the condition to use this syntax.
|
||||
*
|
||||
* @return the required arguments
|
||||
* @return this command condition, null if none
|
||||
*/
|
||||
@NotNull
|
||||
public Argument<?>[] getArguments() {
|
||||
return args;
|
||||
@Nullable
|
||||
public CommandCondition getCommandCondition() {
|
||||
return commandCondition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the command condition of this syntax.
|
||||
* <p>
|
||||
* Be aware that changing the command condition will not automatically update players auto-completion.
|
||||
* You can create a new packet containing the changes with
|
||||
* {@link net.minestom.server.command.CommandManager#createDeclareCommandsPacket(Player)}.
|
||||
*
|
||||
* @param commandCondition the new command condition, null to remove it
|
||||
*/
|
||||
public void setCommandCondition(@Nullable CommandCondition commandCondition) {
|
||||
this.commandCondition = commandCondition;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -46,4 +66,14 @@ public class CommandSyntax {
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the required {@link Argument} for this syntax.
|
||||
*
|
||||
* @return the required arguments
|
||||
*/
|
||||
@NotNull
|
||||
public Argument<?>[] getArguments() {
|
||||
return args;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -29,16 +29,34 @@ public abstract class Argument<T> {
|
||||
|
||||
private ArgumentCallback callback;
|
||||
|
||||
/**
|
||||
* Creates a new argument.
|
||||
*
|
||||
* @param id the id of the argument, used to retrieve the parsed value
|
||||
* @param allowSpace true if the argument can/should have spaces in it
|
||||
* @param useRemaining true if the argument will always take the rest of the command arguments
|
||||
*/
|
||||
public Argument(@NotNull String id, boolean allowSpace, boolean useRemaining) {
|
||||
this.id = id;
|
||||
this.allowSpace = allowSpace;
|
||||
this.useRemaining = useRemaining;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new argument with {@code useRemaining} sets to false.
|
||||
*
|
||||
* @param id the id of the argument, used to retrieve the parsed value
|
||||
* @param allowSpace true if the argument can/should have spaces in it
|
||||
*/
|
||||
public Argument(@NotNull String id, boolean allowSpace) {
|
||||
this(id, allowSpace, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new argument with {@code useRemaining} and {@code allowSpace} sets to false.
|
||||
*
|
||||
* @param id the id of the argument, used to retrieve the parsed value
|
||||
*/
|
||||
public Argument(@NotNull String id) {
|
||||
this(id, false, false);
|
||||
}
|
||||
@ -100,8 +118,8 @@ public abstract class Argument<T> {
|
||||
/**
|
||||
* Gets if the argument always use all the remaining characters.
|
||||
* <p>
|
||||
* ex: /help I am a test - would get you "I am a test"
|
||||
* if the sole argument does use the remaining.
|
||||
* ex: /help I am a test - will always give you "I am a test"
|
||||
* if the first and single argument does use the remaining.
|
||||
*
|
||||
* @return true if the argument use all the remaining characters, false otherwise
|
||||
*/
|
||||
|
@ -12,7 +12,7 @@ public class ArgumentBoolean extends Argument<Boolean> {
|
||||
public static final int NOT_BOOLEAN_ERROR = 1;
|
||||
|
||||
public ArgumentBoolean(String id) {
|
||||
super(id, false);
|
||||
super(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,6 +1,8 @@
|
||||
package net.minestom.server.command.builder.arguments;
|
||||
|
||||
import net.minestom.server.utils.callback.validator.StringArrayValidator;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ -10,6 +12,10 @@ import java.util.regex.Pattern;
|
||||
*/
|
||||
public class ArgumentDynamicStringArray extends Argument<String[]> {
|
||||
|
||||
public static final int RESTRICTION_ERROR = 1;
|
||||
|
||||
private StringArrayValidator dynamicRestriction;
|
||||
|
||||
public ArgumentDynamicStringArray(String id) {
|
||||
super(id, true, true);
|
||||
}
|
||||
@ -27,6 +33,28 @@ public class ArgumentDynamicStringArray extends Argument<String[]> {
|
||||
|
||||
@Override
|
||||
public int getConditionResult(@NotNull String[] value) {
|
||||
|
||||
// true if 'value' is valid based on the dynamic restriction
|
||||
final boolean restrictionCheck = dynamicRestriction == null || dynamicRestriction.isValid(value);
|
||||
|
||||
if (!restrictionCheck) {
|
||||
return RESTRICTION_ERROR;
|
||||
}
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the dynamic restriction of this dynamic argument.
|
||||
* <p>
|
||||
* Will be called once the argument condition is checked.
|
||||
*
|
||||
* @param dynamicRestriction the dynamic restriction, can be null to disable
|
||||
* @return 'this' for chaining
|
||||
*/
|
||||
public ArgumentDynamicStringArray fromRestrictions(@Nullable StringArrayValidator dynamicRestriction) {
|
||||
this.dynamicRestriction = dynamicRestriction;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package net.minestom.server.command.builder.arguments;
|
||||
|
||||
import net.minestom.server.utils.callback.validator.StringValidator;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Same as {@link ArgumentWord} with the exception
|
||||
@ -8,12 +10,20 @@ import org.jetbrains.annotations.NotNull;
|
||||
*/
|
||||
public class ArgumentDynamicWord extends Argument<String> {
|
||||
|
||||
public static final int SPACE_ERROR = 1;
|
||||
public static final int RESTRICTION_ERROR = 2;
|
||||
|
||||
private StringValidator dynamicRestriction;
|
||||
|
||||
public ArgumentDynamicWord(String id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCorrectionResult(@NotNull String value) {
|
||||
if (value.contains(" "))
|
||||
return SPACE_ERROR;
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
@ -25,6 +35,27 @@ public class ArgumentDynamicWord extends Argument<String> {
|
||||
|
||||
@Override
|
||||
public int getConditionResult(@NotNull String value) {
|
||||
|
||||
// true if 'value' is valid based on the dynamic restriction
|
||||
final boolean restrictionCheck = dynamicRestriction == null || dynamicRestriction.isValid(value);
|
||||
|
||||
if (!restrictionCheck) {
|
||||
return RESTRICTION_ERROR;
|
||||
}
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the dynamic restriction of this dynamic argument.
|
||||
* <p>
|
||||
* Will be called once the argument condition is checked.
|
||||
*
|
||||
* @param dynamicRestriction the dynamic restriction, can be null to disable
|
||||
* @return 'this' for chaining
|
||||
*/
|
||||
public ArgumentDynamicWord fromRestrictions(@Nullable StringValidator dynamicRestriction) {
|
||||
this.dynamicRestriction = dynamicRestriction;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,9 @@ import net.minestom.server.command.builder.arguments.number.ArgumentDouble;
|
||||
import net.minestom.server.command.builder.arguments.number.ArgumentFloat;
|
||||
import net.minestom.server.command.builder.arguments.number.ArgumentInteger;
|
||||
import net.minestom.server.command.builder.arguments.number.ArgumentLong;
|
||||
import net.minestom.server.command.builder.arguments.relative.ArgumentRelativeBlockPosition;
|
||||
import net.minestom.server.command.builder.arguments.relative.ArgumentRelativeVec2;
|
||||
import net.minestom.server.command.builder.arguments.relative.ArgumentRelativeVec3;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
@ -109,4 +112,16 @@ public class ArgumentType {
|
||||
return new ArgumentNbtTag(id);
|
||||
}
|
||||
|
||||
public static ArgumentRelativeBlockPosition RelativeBlockPosition(@NotNull String id) {
|
||||
return new ArgumentRelativeBlockPosition(id);
|
||||
}
|
||||
|
||||
public static ArgumentRelativeVec3 RelativeVec3(@NotNull String id) {
|
||||
return new ArgumentRelativeVec3(id);
|
||||
}
|
||||
|
||||
public static ArgumentRelativeVec2 RelativeVec2(@NotNull String id) {
|
||||
return new ArgumentRelativeVec2(id);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import java.util.Arrays;
|
||||
/**
|
||||
* Represents a single word in the command.
|
||||
* <p>
|
||||
* You can specify the only correct words with {@link #from(String...)}.
|
||||
* You can specify the valid words with {@link #from(String...)} (do not abuse it or the client will not be able to join).
|
||||
* <p>
|
||||
* Example: hey
|
||||
*/
|
||||
@ -20,11 +20,13 @@ public class ArgumentWord extends Argument<String> {
|
||||
protected String[] restrictions;
|
||||
|
||||
public ArgumentWord(String id) {
|
||||
super(id, false);
|
||||
super(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to force the use of a few precise words instead of complete freedom.
|
||||
* <p>
|
||||
* WARNING: having an array too long would result in a packet too big or the client being stuck during login.
|
||||
*
|
||||
* @param restrictions the accepted words
|
||||
* @return 'this'
|
||||
@ -73,7 +75,7 @@ public class ArgumentWord extends Argument<String> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the word restrictions
|
||||
* Gets all the word restrictions.
|
||||
*
|
||||
* @return the word restrictions, can be null
|
||||
*/
|
||||
|
@ -16,7 +16,7 @@ public abstract class ArgumentNumber<T extends Number> extends Argument<T> {
|
||||
protected T min, max;
|
||||
|
||||
public ArgumentNumber(String id) {
|
||||
super(id, false);
|
||||
super(id);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
@ -0,0 +1,33 @@
|
||||
package net.minestom.server.command.builder.arguments.relative;
|
||||
|
||||
import net.minestom.server.command.builder.arguments.Argument;
|
||||
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 static final String RELATIVE_CHAR = "~";
|
||||
|
||||
public static final int INVALID_NUMBER_COUNT_ERROR = 1;
|
||||
public static final int INVALID_NUMBER_ERROR = 2;
|
||||
|
||||
private final int numberCount;
|
||||
|
||||
public ArgumentRelative(@NotNull String id, int numberCount) {
|
||||
super(id, true);
|
||||
this.numberCount = numberCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the amount of numbers that this relative location needs.
|
||||
*
|
||||
* @return the amount of coordinate required
|
||||
*/
|
||||
public int getNumberCount() {
|
||||
return numberCount;
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
package net.minestom.server.command.builder.arguments.relative;
|
||||
|
||||
import net.minestom.server.utils.BlockPosition;
|
||||
import net.minestom.server.utils.location.RelativeBlockPosition;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Represents a {@link BlockPosition} with 3 integer numbers (x;y;z) which can take relative coordinates.
|
||||
* <p>
|
||||
* Example: 5 ~ -3
|
||||
*/
|
||||
public class ArgumentRelativeBlockPosition extends ArgumentRelative<RelativeBlockPosition> {
|
||||
|
||||
public ArgumentRelativeBlockPosition(@NotNull String id) {
|
||||
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.startsWith(RELATIVE_CHAR)) {
|
||||
try {
|
||||
// Will throw the exception if not an integer
|
||||
Integer.parseInt(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 an integer
|
||||
Integer.parseInt(potentialNumber);
|
||||
} catch (NumberFormatException | IndexOutOfBoundsException e) {
|
||||
return INVALID_NUMBER_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public RelativeBlockPosition parse(@NotNull String value) {
|
||||
final String[] split = value.split(" ");
|
||||
|
||||
BlockPosition blockPosition = new BlockPosition(0, 0, 0);
|
||||
boolean relativeX = false;
|
||||
boolean relativeY = false;
|
||||
boolean relativeZ = false;
|
||||
|
||||
for (int i = 0; i < split.length; i++) {
|
||||
final String element = split[i];
|
||||
if (element.startsWith(RELATIVE_CHAR)) {
|
||||
|
||||
if (i == 0) {
|
||||
relativeX = true;
|
||||
} else if (i == 1) {
|
||||
relativeY = true;
|
||||
} else if (i == 2) {
|
||||
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 {
|
||||
final int number = Integer.parseInt(element);
|
||||
if (i == 0) {
|
||||
blockPosition.setX(number);
|
||||
} else if (i == 1) {
|
||||
blockPosition.setY(number);
|
||||
} else if (i == 2) {
|
||||
blockPosition.setZ(number);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new RelativeBlockPosition(blockPosition, relativeX, relativeY, relativeZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getConditionResult(@NotNull RelativeBlockPosition value) {
|
||||
return SUCCESS;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package net.minestom.server.command.builder.arguments.relative;
|
||||
|
||||
import net.minestom.server.utils.Vector;
|
||||
import net.minestom.server.utils.location.RelativeVec;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Represents a {@link Vector} with 2 floating numbers (x;z) which can take relative coordinates.
|
||||
* <p>
|
||||
* Example: -1.2 ~
|
||||
*/
|
||||
public class ArgumentRelativeVec2 extends ArgumentRelativeVec {
|
||||
|
||||
public ArgumentRelativeVec2(@NotNull String id) {
|
||||
super(id, 2);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public RelativeVec parse(@NotNull String value) {
|
||||
final String[] split = value.split(" ");
|
||||
|
||||
Vector vector = new Vector();
|
||||
boolean relativeX = false;
|
||||
boolean relativeZ = false;
|
||||
|
||||
for (int i = 0; i < split.length; i++) {
|
||||
final String element = split[i];
|
||||
if (element.startsWith(RELATIVE_CHAR)) {
|
||||
if (i == 0) {
|
||||
relativeX = true;
|
||||
} else if (i == 1) {
|
||||
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 {
|
||||
final float number = Float.parseFloat(element);
|
||||
if (i == 0) {
|
||||
vector.setX(number);
|
||||
} else if (i == 1) {
|
||||
vector.setZ(number);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new RelativeVec(vector, relativeX, false, relativeZ);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package net.minestom.server.command.builder.arguments.relative;
|
||||
|
||||
import net.minestom.server.utils.Vector;
|
||||
import net.minestom.server.utils.location.RelativeVec;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Represents a {@link Vector} with 3 floating numbers (x;y;z) which can take relative coordinates.
|
||||
* <p>
|
||||
* Example: -1.2 ~ 5
|
||||
*/
|
||||
public class ArgumentRelativeVec3 extends ArgumentRelativeVec {
|
||||
|
||||
public ArgumentRelativeVec3(@NotNull String id) {
|
||||
super(id, 3);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public RelativeVec parse(@NotNull String value) {
|
||||
final String[] split = value.split(" ");
|
||||
|
||||
Vector vector = new Vector();
|
||||
boolean relativeX = false;
|
||||
boolean relativeY = false;
|
||||
boolean relativeZ = false;
|
||||
|
||||
for (int i = 0; i < split.length; i++) {
|
||||
final String element = split[i];
|
||||
if (element.startsWith(RELATIVE_CHAR)) {
|
||||
|
||||
if (i == 0) {
|
||||
relativeX = true;
|
||||
} else if (i == 1) {
|
||||
relativeY = true;
|
||||
} else if (i == 2) {
|
||||
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 {
|
||||
final float number = Float.parseFloat(element);
|
||||
if (i == 0) {
|
||||
vector.setX(number);
|
||||
} else if (i == 1) {
|
||||
vector.setY(number);
|
||||
} else if (i == 2) {
|
||||
vector.setZ(number);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new RelativeVec(vector, relativeX, relativeY, relativeZ);
|
||||
}
|
||||
}
|
@ -2,10 +2,30 @@ package net.minestom.server.command.builder.condition;
|
||||
|
||||
import net.minestom.server.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Used to know if the {@link CommandSender} is allowed to run the command.
|
||||
* Used to know if the {@link CommandSender} is allowed to run the command or a specific syntax.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface CommandCondition {
|
||||
boolean apply(@NotNull CommandSender source);
|
||||
|
||||
/**
|
||||
* Called when the sender permission needs to be checked.
|
||||
* <p>
|
||||
* The first time will be during player connection in order to know
|
||||
* if the command/syntax should be displayed as tab-completion suggestion,
|
||||
* {@code commandString} will be null in this case. (It is also possible for the command string
|
||||
* to be null if a new command packet is built)
|
||||
* <p>
|
||||
* Otherwise, {@code commandString} will never be null
|
||||
* but will instead be the raw command string given by the sender.
|
||||
* You should in this case warn the sender (eg by sending a message) if the condition is unsuccessful.
|
||||
*
|
||||
* @param source the sender of the command
|
||||
* @param commandString the raw command string,
|
||||
* null if this is an access request
|
||||
* @return true if the sender has the right to use the command, false otherwise
|
||||
*/
|
||||
boolean canUse(@NotNull CommandSender source, @Nullable String commandString);
|
||||
}
|
||||
|
@ -56,11 +56,25 @@ public interface Data {
|
||||
*
|
||||
* @param key the key
|
||||
* @param value the value object, null to remove the key
|
||||
* @param type the value type, can be null if not in a {@link SerializableData}
|
||||
* @param type the value type, {@link #set(String, Object)} can be used instead.
|
||||
* null if {@code value} is also null
|
||||
* @param <T> the value generic
|
||||
*/
|
||||
<T> void set(@NotNull String key, @Nullable T value, @Nullable Class<T> type);
|
||||
|
||||
/**
|
||||
* Assigns a value to a specific key.
|
||||
* <p>
|
||||
* Will by default call {@link #set(String, Object, Class)} with the type sets to {@link T#getClass()}.
|
||||
*
|
||||
* @param key the key
|
||||
* @param value the value object, null to remove the key
|
||||
* @param <T> the value generic
|
||||
*/
|
||||
default <T> void set(@NotNull String key, @Nullable T value) {
|
||||
set(key, value, value != null ? (Class<T>) value.getClass() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a value based on its key.
|
||||
*
|
||||
|
@ -26,7 +26,7 @@ public interface DataContainer {
|
||||
* Default implementations are {@link DataImpl} and {@link SerializableDataImpl} depending
|
||||
* on your use-case.
|
||||
*
|
||||
* @param data the {@link Data} of this container, null to remove it
|
||||
* @param data the new {@link Data} of this container, null to remove it
|
||||
*/
|
||||
void setData(@Nullable Data data);
|
||||
|
||||
|
@ -14,12 +14,20 @@ public class DataImpl implements Data {
|
||||
|
||||
protected final ConcurrentHashMap<String, Object> data = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Data key = Class
|
||||
* Used to know the type of an element of this data object (for serialization purpose)
|
||||
*/
|
||||
protected final ConcurrentHashMap<String, Class> dataType = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public <T> void set(@NotNull String key, @Nullable T value, @Nullable Class<T> type) {
|
||||
public synchronized <T> void set(@NotNull String key, @Nullable T value, @Nullable Class<T> type) {
|
||||
if (value != null) {
|
||||
this.data.put(key, value);
|
||||
this.dataType.put(key, type);
|
||||
} else {
|
||||
this.data.remove(key);
|
||||
this.dataType.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,6 +62,7 @@ public class DataImpl implements Data {
|
||||
public Data copy() {
|
||||
DataImpl data = new DataImpl();
|
||||
data.data.putAll(this.data);
|
||||
data.dataType.putAll(this.dataType);
|
||||
return data;
|
||||
}
|
||||
|
||||
|
@ -86,8 +86,7 @@ public final class DataManager {
|
||||
*
|
||||
* @param clazz the data class
|
||||
* @param <T> the data type
|
||||
* @return the {@link DataType} associated to the class
|
||||
* @throws NullPointerException if none is found
|
||||
* @return the {@link DataType} associated to the class, null if not found
|
||||
*/
|
||||
@Nullable
|
||||
public <T> DataType<T> getDataType(@NotNull Class<T> clazz) {
|
||||
|
53
src/main/java/net/minestom/server/data/NbtDataImpl.java
Normal file
53
src/main/java/net/minestom/server/data/NbtDataImpl.java
Normal file
@ -0,0 +1,53 @@
|
||||
package net.minestom.server.data;
|
||||
|
||||
import net.minestom.server.utils.NBTUtils;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBT;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A data implementation backed by a {@link org.jglrxavpok.hephaistos.nbt.NBTCompound}.
|
||||
*/
|
||||
public class NbtDataImpl extends DataImpl {
|
||||
|
||||
// Used to know if a nbt key is from a Data object, should NOT be changed
|
||||
public static final String KEY_PREFIX = "nbtdata_";
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Data copy() {
|
||||
DataImpl data = new NbtDataImpl();
|
||||
data.data.putAll(this.data);
|
||||
data.dataType.putAll(this.dataType);
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes all the data into a {@link NBTCompound}.
|
||||
*
|
||||
* @param nbtCompound the nbt compound to write to
|
||||
* @throws NullPointerException if the type of a data is not a primitive nbt type and therefore not supported
|
||||
* (you can use {@link DataType#encode(BinaryWriter, Object)} to use byte array instead)
|
||||
*/
|
||||
public void writeToNbt(@NotNull NBTCompound nbtCompound) {
|
||||
for (Map.Entry<String, Object> entry : data.entrySet()) {
|
||||
final String key = entry.getKey();
|
||||
final Object value = entry.getValue();
|
||||
|
||||
final Class type = dataType.get(key);
|
||||
|
||||
final NBT nbt = NBTUtils.toNBT(value, type, false);
|
||||
|
||||
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");
|
||||
|
||||
final String finalKey = KEY_PREFIX + key;
|
||||
nbtCompound.set(finalKey, nbt);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -14,7 +14,7 @@ import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* {@link SerializableData} implementation based on {@link DataImpl}
|
||||
* {@link SerializableData} implementation based on {@link DataImpl}.
|
||||
*/
|
||||
public class SerializableDataImpl extends DataImpl implements SerializableData {
|
||||
|
||||
@ -25,15 +25,9 @@ public class SerializableDataImpl extends DataImpl implements SerializableData {
|
||||
private static final ConcurrentHashMap<String, Class> nameToClassMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Data key -> Class
|
||||
* Used to know the type of an element of this data object (for serialization purpose)
|
||||
*/
|
||||
private final ConcurrentHashMap<String, Class> dataType = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Set a value to a specific key
|
||||
* Sets a value to a specific key.
|
||||
* <p>
|
||||
* WARNING: the type needs to be registered in {@link DataManager}
|
||||
* WARNING: the type needs to be registered in {@link DataManager}.
|
||||
*
|
||||
* @param key the key
|
||||
* @param value the value object
|
||||
@ -42,18 +36,12 @@ public class SerializableDataImpl extends DataImpl implements SerializableData {
|
||||
* @throws UnsupportedOperationException if {@code type} is not registered in {@link DataManager}
|
||||
*/
|
||||
@Override
|
||||
public <T> void set(@NotNull String key, @Nullable T value, @NotNull Class<T> type) {
|
||||
if (value != null) {
|
||||
if (DATA_MANAGER.getDataType(type) == null) {
|
||||
throw new UnsupportedOperationException("Type " + type.getName() + " hasn't been registered in DataManager#registerType");
|
||||
}
|
||||
|
||||
this.data.put(key, value);
|
||||
this.dataType.put(key, type);
|
||||
} else {
|
||||
this.data.remove(key);
|
||||
this.dataType.remove(key);
|
||||
public <T> void set(@NotNull String key, @Nullable T value, @Nullable Class<T> type) {
|
||||
if (type != null && DATA_MANAGER.getDataType(type) == null) {
|
||||
throw new UnsupportedOperationException("Type " + type.getName() + " hasn't been registered in DataManager#registerType");
|
||||
}
|
||||
|
||||
super.set(key, value, type);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
@ -21,7 +21,6 @@ import net.minestom.server.instance.WorldBorder;
|
||||
import net.minestom.server.instance.block.CustomBlock;
|
||||
import net.minestom.server.network.packet.server.play.*;
|
||||
import net.minestom.server.thread.ThreadProvider;
|
||||
import net.minestom.server.utils.ArrayUtils;
|
||||
import net.minestom.server.utils.BlockPosition;
|
||||
import net.minestom.server.utils.Position;
|
||||
import net.minestom.server.utils.Vector;
|
||||
@ -89,7 +88,7 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer {
|
||||
private boolean autoViewable;
|
||||
private final int id;
|
||||
private Data data;
|
||||
private final Set<Player> viewers = new CopyOnWriteArraySet<>();
|
||||
protected final Set<Player> viewers = new CopyOnWriteArraySet<>();
|
||||
|
||||
protected UUID uuid;
|
||||
private boolean isActive; // False if entity has only been instanced without being added somewhere
|
||||
@ -1012,7 +1011,12 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer {
|
||||
instance.removeEntityFromChunk(this, lastChunk);
|
||||
instance.addEntityToChunk(this, newChunk);
|
||||
}
|
||||
updateView(lastChunk, newChunk);
|
||||
if (this instanceof Player) {
|
||||
// Refresh player view
|
||||
final Player player = (Player) this;
|
||||
player.refreshVisibleChunks(newChunk);
|
||||
player.refreshVisibleEntities(newChunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1025,72 +1029,6 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer {
|
||||
refreshPosition(position.getX(), position.getY(), position.getZ());
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages viewable entities automatically if {@link #isAutoViewable()} is enabled.
|
||||
* <p>
|
||||
* Called by {@link #refreshPosition(float, float, float)} when the new position is in a different {@link Chunk}.
|
||||
*
|
||||
* @param lastChunk the previous {@link Chunk} of this entity
|
||||
* @param newChunk the new {@link Chunk} of this entity
|
||||
*/
|
||||
private void updateView(@NotNull Chunk lastChunk, @NotNull Chunk newChunk) {
|
||||
final boolean isPlayer = this instanceof Player;
|
||||
|
||||
if (isPlayer)
|
||||
((Player) this).refreshVisibleChunks(newChunk); // Refresh loaded chunk
|
||||
|
||||
// Refresh entity viewable list
|
||||
final int entityViewDistance = MinecraftServer.getEntityViewDistance();
|
||||
final long[] lastVisibleChunksEntity = ChunkUtils.getChunksInRange(new Position(16 * lastChunk.getChunkX(), 0, 16 * lastChunk.getChunkZ()), entityViewDistance);
|
||||
final long[] updatedVisibleChunksEntity = ChunkUtils.getChunksInRange(new Position(16 * newChunk.getChunkX(), 0, 16 * newChunk.getChunkZ()), entityViewDistance);
|
||||
|
||||
// Remove from previous chunks
|
||||
final int[] oldChunksEntity = ArrayUtils.getDifferencesBetweenArray(lastVisibleChunksEntity, updatedVisibleChunksEntity);
|
||||
for (int index : oldChunksEntity) {
|
||||
final long chunkIndex = lastVisibleChunksEntity[index];
|
||||
final int chunkX = ChunkUtils.getChunkCoordX(chunkIndex);
|
||||
final int chunkZ = ChunkUtils.getChunkCoordZ(chunkIndex);
|
||||
final Chunk chunk = instance.getChunk(chunkX, chunkZ);
|
||||
if (chunk == null)
|
||||
continue;
|
||||
instance.getChunkEntities(chunk).forEach(ent -> {
|
||||
if (ent instanceof Player) {
|
||||
final Player player = (Player) ent;
|
||||
if (isAutoViewable())
|
||||
removeViewer(player);
|
||||
if (isPlayer) {
|
||||
player.removeViewer((Player) this);
|
||||
}
|
||||
} else if (isPlayer) {
|
||||
ent.removeViewer((Player) this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Add to new chunks
|
||||
final int[] newChunksEntity = ArrayUtils.getDifferencesBetweenArray(updatedVisibleChunksEntity, lastVisibleChunksEntity);
|
||||
for (int index : newChunksEntity) {
|
||||
final long chunkIndex = updatedVisibleChunksEntity[index];
|
||||
final int chunkX = ChunkUtils.getChunkCoordX(chunkIndex);
|
||||
final int chunkZ = ChunkUtils.getChunkCoordZ(chunkIndex);
|
||||
final Chunk chunk = instance.getChunk(chunkX, chunkZ);
|
||||
if (chunk == null)
|
||||
continue;
|
||||
instance.getChunkEntities(chunk).forEach(ent -> {
|
||||
if (ent instanceof Player) {
|
||||
Player player = (Player) ent;
|
||||
if (isAutoViewable())
|
||||
addViewer(player);
|
||||
if (this instanceof Player) {
|
||||
player.addViewer((Player) this);
|
||||
}
|
||||
} else if (isPlayer) {
|
||||
ent.addViewer((Player) this);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the entity view internally.
|
||||
* <p>
|
||||
|
@ -14,7 +14,7 @@ import net.minestom.server.instance.Instance;
|
||||
import net.minestom.server.instance.WorldBorder;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.network.packet.server.play.EntityEquipmentPacket;
|
||||
import net.minestom.server.network.packet.server.play.EntityPacket;
|
||||
import net.minestom.server.network.packet.server.play.EntityMovementPacket;
|
||||
import net.minestom.server.network.packet.server.play.SpawnLivingEntityPacket;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.utils.Position;
|
||||
@ -176,8 +176,8 @@ public abstract class EntityCreature extends LivingEntity {
|
||||
|
||||
final PlayerConnection playerConnection = player.getPlayerConnection();
|
||||
|
||||
EntityPacket entityPacket = new EntityPacket();
|
||||
entityPacket.entityId = getEntityId();
|
||||
EntityMovementPacket entityMovementPacket = new EntityMovementPacket();
|
||||
entityMovementPacket.entityId = getEntityId();
|
||||
|
||||
SpawnLivingEntityPacket spawnLivingEntityPacket = new SpawnLivingEntityPacket();
|
||||
spawnLivingEntityPacket.entityId = getEntityId();
|
||||
@ -186,7 +186,7 @@ public abstract class EntityCreature extends LivingEntity {
|
||||
spawnLivingEntityPacket.position = getPosition();
|
||||
spawnLivingEntityPacket.headPitch = 0;
|
||||
|
||||
playerConnection.sendPacket(entityPacket);
|
||||
playerConnection.sendPacket(entityMovementPacket);
|
||||
playerConnection.sendPacket(spawnLivingEntityPacket);
|
||||
playerConnection.sendPacket(getVelocityPacket());
|
||||
playerConnection.sendPacket(getMetadataPacket());
|
||||
|
@ -34,7 +34,6 @@ import net.minestom.server.network.PlayerProvider;
|
||||
import net.minestom.server.network.packet.client.ClientPlayPacket;
|
||||
import net.minestom.server.network.packet.client.play.ClientChatMessagePacket;
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import net.minestom.server.network.packet.server.login.JoinGamePacket;
|
||||
import net.minestom.server.network.packet.server.play.*;
|
||||
import net.minestom.server.network.player.NettyPlayerConnection;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
@ -80,6 +79,7 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
|
||||
private String username;
|
||||
protected final PlayerConnection playerConnection;
|
||||
// All the entities that this player can see
|
||||
protected final Set<Entity> viewableEntities = new CopyOnWriteArraySet<>();
|
||||
|
||||
private int latency;
|
||||
@ -398,63 +398,72 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
// Multiplayer sync
|
||||
final boolean positionChanged = position.getX() != lastX || position.getY() != lastY || position.getZ() != lastZ;
|
||||
final boolean viewChanged = position.getYaw() != lastYaw || position.getPitch() != lastPitch;
|
||||
if (!getViewers().isEmpty() && (positionChanged || viewChanged)) {
|
||||
ServerPacket updatePacket;
|
||||
ServerPacket optionalUpdatePacket = null;
|
||||
if (positionChanged && viewChanged) {
|
||||
EntityPositionAndRotationPacket entityPositionAndRotationPacket = new EntityPositionAndRotationPacket();
|
||||
entityPositionAndRotationPacket.entityId = getEntityId();
|
||||
entityPositionAndRotationPacket.deltaX = (short) ((position.getX() * 32 - lastX * 32) * 128);
|
||||
entityPositionAndRotationPacket.deltaY = (short) ((position.getY() * 32 - lastY * 32) * 128);
|
||||
entityPositionAndRotationPacket.deltaZ = (short) ((position.getZ() * 32 - lastZ * 32) * 128);
|
||||
entityPositionAndRotationPacket.yaw = position.getYaw();
|
||||
entityPositionAndRotationPacket.pitch = position.getPitch();
|
||||
entityPositionAndRotationPacket.onGround = onGround;
|
||||
if (!viewers.isEmpty()) {
|
||||
if (positionChanged || viewChanged) {
|
||||
// Player moved since last time
|
||||
|
||||
ServerPacket updatePacket;
|
||||
ServerPacket optionalUpdatePacket = null;
|
||||
if (positionChanged && viewChanged) {
|
||||
EntityPositionAndRotationPacket entityPositionAndRotationPacket = new EntityPositionAndRotationPacket();
|
||||
entityPositionAndRotationPacket.entityId = getEntityId();
|
||||
entityPositionAndRotationPacket.deltaX = (short) ((position.getX() * 32 - lastX * 32) * 128);
|
||||
entityPositionAndRotationPacket.deltaY = (short) ((position.getY() * 32 - lastY * 32) * 128);
|
||||
entityPositionAndRotationPacket.deltaZ = (short) ((position.getZ() * 32 - lastZ * 32) * 128);
|
||||
entityPositionAndRotationPacket.yaw = position.getYaw();
|
||||
entityPositionAndRotationPacket.pitch = position.getPitch();
|
||||
entityPositionAndRotationPacket.onGround = onGround;
|
||||
|
||||
lastX = position.getX();
|
||||
lastY = position.getY();
|
||||
lastZ = position.getZ();
|
||||
lastYaw = position.getYaw();
|
||||
lastPitch = position.getPitch();
|
||||
updatePacket = entityPositionAndRotationPacket;
|
||||
} else if (positionChanged) {
|
||||
EntityPositionPacket entityPositionPacket = new EntityPositionPacket();
|
||||
entityPositionPacket.entityId = getEntityId();
|
||||
entityPositionPacket.deltaX = (short) ((position.getX() * 32 - lastX * 32) * 128);
|
||||
entityPositionPacket.deltaY = (short) ((position.getY() * 32 - lastY * 32) * 128);
|
||||
entityPositionPacket.deltaZ = (short) ((position.getZ() * 32 - lastZ * 32) * 128);
|
||||
entityPositionPacket.onGround = onGround;
|
||||
lastX = position.getX();
|
||||
lastY = position.getY();
|
||||
lastZ = position.getZ();
|
||||
updatePacket = entityPositionPacket;
|
||||
} else {
|
||||
// View changed
|
||||
EntityRotationPacket entityRotationPacket = new EntityRotationPacket();
|
||||
entityRotationPacket.entityId = getEntityId();
|
||||
entityRotationPacket.yaw = position.getYaw();
|
||||
entityRotationPacket.pitch = position.getPitch();
|
||||
entityRotationPacket.onGround = onGround;
|
||||
|
||||
lastYaw = position.getYaw();
|
||||
lastPitch = position.getPitch();
|
||||
updatePacket = entityRotationPacket;
|
||||
}
|
||||
|
||||
if (viewChanged) {
|
||||
EntityHeadLookPacket entityHeadLookPacket = new EntityHeadLookPacket();
|
||||
entityHeadLookPacket.entityId = getEntityId();
|
||||
entityHeadLookPacket.yaw = position.getYaw();
|
||||
optionalUpdatePacket = entityHeadLookPacket;
|
||||
}
|
||||
|
||||
// Send the update packet
|
||||
if (optionalUpdatePacket != null) {
|
||||
sendPacketsToViewers(updatePacket, optionalUpdatePacket);
|
||||
} else {
|
||||
sendPacketToViewers(updatePacket);
|
||||
}
|
||||
|
||||
lastX = position.getX();
|
||||
lastY = position.getY();
|
||||
lastZ = position.getZ();
|
||||
lastYaw = position.getYaw();
|
||||
lastPitch = position.getPitch();
|
||||
updatePacket = entityPositionAndRotationPacket;
|
||||
} else if (positionChanged) {
|
||||
EntityPositionPacket entityPositionPacket = new EntityPositionPacket();
|
||||
entityPositionPacket.entityId = getEntityId();
|
||||
entityPositionPacket.deltaX = (short) ((position.getX() * 32 - lastX * 32) * 128);
|
||||
entityPositionPacket.deltaY = (short) ((position.getY() * 32 - lastY * 32) * 128);
|
||||
entityPositionPacket.deltaZ = (short) ((position.getZ() * 32 - lastZ * 32) * 128);
|
||||
entityPositionPacket.onGround = onGround;
|
||||
lastX = position.getX();
|
||||
lastY = position.getY();
|
||||
lastZ = position.getZ();
|
||||
updatePacket = entityPositionPacket;
|
||||
} else {
|
||||
// View changed
|
||||
EntityRotationPacket entityRotationPacket = new EntityRotationPacket();
|
||||
entityRotationPacket.entityId = getEntityId();
|
||||
entityRotationPacket.yaw = position.getYaw();
|
||||
entityRotationPacket.pitch = position.getPitch();
|
||||
entityRotationPacket.onGround = onGround;
|
||||
|
||||
lastYaw = position.getYaw();
|
||||
lastPitch = position.getPitch();
|
||||
updatePacket = entityRotationPacket;
|
||||
// Player did not move since last time
|
||||
EntityMovementPacket entityMovementPacket = new EntityMovementPacket();
|
||||
entityMovementPacket.entityId = getEntityId();
|
||||
sendPacketToViewers(entityMovementPacket);
|
||||
}
|
||||
|
||||
if (viewChanged) {
|
||||
EntityHeadLookPacket entityHeadLookPacket = new EntityHeadLookPacket();
|
||||
entityHeadLookPacket.entityId = getEntityId();
|
||||
entityHeadLookPacket.yaw = position.getYaw();
|
||||
optionalUpdatePacket = entityHeadLookPacket;
|
||||
}
|
||||
|
||||
// Send the update packet
|
||||
if (optionalUpdatePacket != null) {
|
||||
sendPacketsToViewers(updatePacket, optionalUpdatePacket);
|
||||
} else {
|
||||
sendPacketToViewers(updatePacket);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1028,6 +1037,7 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
* Sets and refresh client food bar.
|
||||
*
|
||||
* @param food the new food value
|
||||
* @throws IllegalArgumentException if {@code food} is not between 0 and 20
|
||||
*/
|
||||
public void setFood(int food) {
|
||||
Check.argCondition(!MathUtils.isBetween(food, 0, 20), "Food has to be between 0 and 20");
|
||||
@ -1043,6 +1053,7 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
* Sets and refresh client food saturation.
|
||||
*
|
||||
* @param foodSaturation the food saturation
|
||||
* @throws IllegalArgumentException if {@code foodSaturation} is not between 0 and 5
|
||||
*/
|
||||
public void setFoodSaturation(float foodSaturation) {
|
||||
Check.argCondition(!MathUtils.isBetween(foodSaturation, 0, 5), "Food saturation has to be between 0 and 5");
|
||||
@ -1217,7 +1228,7 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
*
|
||||
* @param resourcePack the resource pack
|
||||
*/
|
||||
public void setResourcePack(ResourcePack resourcePack) {
|
||||
public void setResourcePack(@NotNull ResourcePack resourcePack) {
|
||||
Check.notNull(resourcePack, "The resource pack cannot be null");
|
||||
final String url = resourcePack.getUrl();
|
||||
final String hash = resourcePack.getHash();
|
||||
@ -1366,6 +1377,7 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
* This cannot change the displayed level, see {@link #setLevel(int)}.
|
||||
*
|
||||
* @param exp a percentage between 0 and 1
|
||||
* @throws IllegalArgumentException if {@code exp} is not between 0 and 1
|
||||
*/
|
||||
public void setExp(float exp) {
|
||||
Check.argCondition(!MathUtils.isBetween(exp, 0, 1), "Exp should be between 0 and 1");
|
||||
@ -1406,12 +1418,12 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
|
||||
/**
|
||||
* Called when the player changes chunk (move from one to another).
|
||||
* Can also be used to refresh the list of chunks that the client should see.
|
||||
* Can also be used to refresh the list of chunks that the client should see based on {@link #getChunkRange()}.
|
||||
* <p>
|
||||
* It does remove and add the player from the chunks viewers list when removed or added.
|
||||
* It also calls the events {@link PlayerChunkUnloadEvent} and {@link PlayerChunkLoadEvent}.
|
||||
*
|
||||
* @param newChunk the current/new player chunk
|
||||
* @param newChunk the current/new player chunk (can be the current one)
|
||||
*/
|
||||
public void refreshVisibleChunks(@NotNull Chunk newChunk) {
|
||||
// Previous chunks indexes
|
||||
@ -1420,7 +1432,7 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
).toArray();
|
||||
|
||||
// New chunks indexes
|
||||
final long[] updatedVisibleChunks = ChunkUtils.getChunksInRange(new Position(16 * newChunk.getChunkX(), 0, 16 * newChunk.getChunkZ()), getChunkRange());
|
||||
final long[] updatedVisibleChunks = ChunkUtils.getChunksInRange(newChunk.toPosition(), getChunkRange());
|
||||
|
||||
// Find the difference between the two arrays¬
|
||||
final int[] oldChunks = ArrayUtils.getDifferencesBetweenArray(lastVisibleChunks, updatedVisibleChunks);
|
||||
@ -1442,7 +1454,7 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
chunk.removeViewer(this);
|
||||
}
|
||||
|
||||
// Not sure what it does...
|
||||
// Update client render distance
|
||||
updateViewPosition(newChunk);
|
||||
|
||||
// Load new chunks
|
||||
@ -1461,6 +1473,52 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the list of entities that the player should be able to see based on {@link MinecraftServer#getEntityViewDistance()}
|
||||
* and {@link Entity#isAutoViewable()}.
|
||||
*
|
||||
* @param newChunk the new chunk of the player (can be the current one)
|
||||
*/
|
||||
public void refreshVisibleEntities(@NotNull Chunk newChunk) {
|
||||
final int entityViewDistance = MinecraftServer.getEntityViewDistance();
|
||||
final float maximalDistance = entityViewDistance * Chunk.CHUNK_SECTION_SIZE;
|
||||
|
||||
// Manage already viewable entities
|
||||
this.viewableEntities.forEach(entity -> {
|
||||
final float distance = entity.getDistance(this);
|
||||
if (distance > maximalDistance) {
|
||||
// Entity shouldn't be viewable anymore
|
||||
if (isAutoViewable()) {
|
||||
entity.removeViewer(this);
|
||||
}
|
||||
if (entity instanceof Player && entity.isAutoViewable()) {
|
||||
removeViewer((Player) entity);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Manage entities in unchecked chunks
|
||||
final long[] chunksInRange = ChunkUtils.getChunksInRange(newChunk.toPosition(), entityViewDistance);
|
||||
|
||||
for (long chunkIndex : chunksInRange) {
|
||||
final int chunkX = ChunkUtils.getChunkCoordX(chunkIndex);
|
||||
final int chunkZ = ChunkUtils.getChunkCoordZ(chunkIndex);
|
||||
final Chunk chunk = instance.getChunk(chunkX, chunkZ);
|
||||
if (chunk == null)
|
||||
continue;
|
||||
instance.getChunkEntities(chunk).forEach(entity -> {
|
||||
if (isAutoViewable() && !entity.viewers.contains(this)) {
|
||||
entity.addViewer(this);
|
||||
}
|
||||
|
||||
if (entity instanceof Player && entity.isAutoViewable() && !viewers.contains(entity)) {
|
||||
addViewer((Player) entity);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void teleport(@NotNull Position position, @Nullable Runnable callback) {
|
||||
super.teleport(position, () -> {
|
||||
@ -1833,6 +1891,7 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
* Changes the player permission level.
|
||||
*
|
||||
* @param permissionLevel the new player permission level
|
||||
* @throws IllegalArgumentException if {@code permissionLevel} is not between 0 and 4
|
||||
*/
|
||||
public void setPermissionLevel(int permissionLevel) {
|
||||
Check.argCondition(!MathUtils.isBetween(permissionLevel, 0, 4), "permissionLevel has to be between 0 and 4");
|
||||
|
@ -86,9 +86,11 @@ public class PlayerSkin {
|
||||
final String url = "https://api.mojang.com/users/profiles/minecraft/" + username;
|
||||
|
||||
try {
|
||||
// Retrieve the mojang uuid from the name
|
||||
final String response = URLUtils.getText(url);
|
||||
final JsonObject jsonObject = JsonParser.parseString(response).getAsJsonObject();
|
||||
final String uuid = jsonObject.get("id").getAsString();
|
||||
// Retrieve the skin data from the mojang uuid
|
||||
return fromUuid(uuid);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
|
@ -3,13 +3,15 @@ package net.minestom.server.extras;
|
||||
import lombok.Getter;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
|
||||
public class MojangAuth {
|
||||
public final class MojangAuth {
|
||||
|
||||
@Getter
|
||||
private static boolean usingMojangAuth = false;
|
||||
|
||||
/**
|
||||
* Enable mojang authentication on the server.
|
||||
* Enables mojang authentication on the server.
|
||||
* <p>
|
||||
* Be aware that enabling a proxy will make Mojang authentication ignored.
|
||||
*/
|
||||
public static void init() {
|
||||
if (MinecraftServer.getNettyServer().getAddress() == null) {
|
||||
|
@ -7,7 +7,7 @@ import net.minestom.server.instance.block.rule.vanilla.AxisPlacementRule;
|
||||
import net.minestom.server.instance.block.rule.vanilla.RedstonePlacementRule;
|
||||
import net.minestom.server.instance.block.rule.vanilla.WallPlacementRule;
|
||||
|
||||
public class PlacementRules {
|
||||
public final class PlacementRules {
|
||||
|
||||
public static void init() {
|
||||
BlockManager blockManager = MinecraftServer.getBlockManager();
|
||||
|
@ -0,0 +1,27 @@
|
||||
package net.minestom.server.extras.bungee;
|
||||
|
||||
/**
|
||||
* BungeeCord forwarding support. This does not count as a security feature and you will still be required to manage your firewall.
|
||||
* <p>
|
||||
* Please consider using {@link net.minestom.server.extras.velocity.VelocityProxy} instead.
|
||||
*/
|
||||
public final class BungeeCordProxy {
|
||||
|
||||
private static boolean enabled;
|
||||
|
||||
/**
|
||||
* Enables bungee IP forwarding.
|
||||
*/
|
||||
public static void enable() {
|
||||
BungeeCordProxy.enabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets if bungee IP forwarding is enabled.
|
||||
*
|
||||
* @return true if forwarding is enabled
|
||||
*/
|
||||
public static boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.*;
|
||||
|
||||
public class MojangCrypt {
|
||||
public final class MojangCrypt {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
@Nullable
|
||||
|
@ -0,0 +1,78 @@
|
||||
package net.minestom.server.extras.velocity;
|
||||
|
||||
import com.google.common.net.InetAddresses;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minestom.server.utils.binary.BinaryReader;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.net.InetAddress;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
/**
|
||||
* Support for <a href="https://velocitypowered.com/">Velocity</a> modern forwarding.
|
||||
* <p>
|
||||
* Can be enabled by simply calling {@link #enable(String)}.
|
||||
*/
|
||||
public final class VelocityProxy {
|
||||
|
||||
public static final String PLAYER_INFO_CHANNEL = "velocity:player_info";
|
||||
private static final int SUPPORTED_FORWARDING_VERSION = 1;
|
||||
|
||||
private static boolean enabled;
|
||||
private static byte[] secret;
|
||||
|
||||
/**
|
||||
* Enables velocity modern forwarding.
|
||||
*
|
||||
* @param secret the forwarding secret,
|
||||
* be sure to do not hardcode it in your code but to retrieve it from a file or anywhere else safe
|
||||
*/
|
||||
public static void enable(@NotNull String secret) {
|
||||
VelocityProxy.enabled = true;
|
||||
VelocityProxy.secret = secret.getBytes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets if velocity modern forwarding is enabled.
|
||||
*
|
||||
* @return true if velocity modern forwarding is enabled
|
||||
*/
|
||||
public static boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public static boolean checkIntegrity(@NotNull BinaryReader reader) {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final byte[] signature = reader.readBytes(32);
|
||||
|
||||
ByteBuf buf = reader.getBuffer();
|
||||
final byte[] data = new byte[buf.readableBytes()];
|
||||
buf.getBytes(buf.readerIndex(), data);
|
||||
|
||||
try {
|
||||
final Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(new SecretKeySpec(secret, "HmacSHA256"));
|
||||
final byte[] mySignature = mac.doFinal(data);
|
||||
if (!MessageDigest.isEqual(signature, mySignature)) {
|
||||
return false;
|
||||
}
|
||||
} catch (final InvalidKeyException | NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
final int version = reader.readVarInt();
|
||||
return version == SUPPORTED_FORWARDING_VERSION;
|
||||
}
|
||||
|
||||
public static InetAddress readAddress(@NotNull BinaryReader reader) {
|
||||
return InetAddresses.forString(reader.readSizedString());
|
||||
}
|
||||
|
||||
}
|
@ -76,7 +76,7 @@ public interface BlockModifier {
|
||||
setBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), block);
|
||||
}
|
||||
|
||||
default void setBlockStateId(BlockPosition blockPosition, short blockStateId) {
|
||||
default void setBlockStateId(@NotNull BlockPosition blockPosition, short blockStateId) {
|
||||
Check.notNull(blockPosition, "The block position cannot be null");
|
||||
setBlockStateId(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), blockStateId);
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import net.minestom.server.network.packet.server.play.ChunkDataPacket;
|
||||
import net.minestom.server.network.packet.server.play.UpdateLightPacket;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.utils.MathUtils;
|
||||
import net.minestom.server.utils.Position;
|
||||
import net.minestom.server.utils.binary.BinaryReader;
|
||||
import net.minestom.server.utils.chunk.ChunkCallback;
|
||||
import net.minestom.server.utils.chunk.ChunkSupplier;
|
||||
@ -57,8 +58,6 @@ public abstract class Chunk implements Viewable, DataContainer {
|
||||
|
||||
public static final int BIOME_COUNT = 1024; // 4x4x4 blocks group
|
||||
|
||||
@NotNull
|
||||
protected final Instance instance;
|
||||
@NotNull
|
||||
protected final Biome[] biomes;
|
||||
protected final int chunkX, chunkZ;
|
||||
@ -76,8 +75,7 @@ public abstract class Chunk implements Viewable, DataContainer {
|
||||
// Data
|
||||
protected Data data;
|
||||
|
||||
public Chunk(@NotNull Instance instance, @Nullable Biome[] biomes, int chunkX, int chunkZ, boolean shouldGenerate) {
|
||||
this.instance = instance;
|
||||
public Chunk(@Nullable Biome[] biomes, int chunkX, int chunkZ, boolean shouldGenerate) {
|
||||
this.chunkX = chunkX;
|
||||
this.chunkZ = chunkZ;
|
||||
this.shouldGenerate = shouldGenerate;
|
||||
@ -225,13 +223,12 @@ public abstract class Chunk implements Viewable, DataContainer {
|
||||
* <p>
|
||||
* The instance and chunk position (X/Z) can be modified using the given arguments.
|
||||
*
|
||||
* @param instance the instance of the new chunk
|
||||
* @param chunkX the new chunk X
|
||||
* @param chunkZ the new chunK Z
|
||||
* @param chunkX the new chunk X
|
||||
* @param chunkZ the new chunk Z
|
||||
* @return a copy of this chunk with a potentially new instance and position
|
||||
*/
|
||||
@NotNull
|
||||
public abstract Chunk copy(@NotNull Instance instance, int chunkX, int chunkZ);
|
||||
public abstract Chunk copy(int chunkX, int chunkZ);
|
||||
|
||||
/**
|
||||
* Gets the {@link CustomBlock} at a position.
|
||||
@ -281,8 +278,19 @@ public abstract class Chunk implements Viewable, DataContainer {
|
||||
return chunkZ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Position} object based on this chunk.
|
||||
*
|
||||
* @return the position of this chunk
|
||||
*/
|
||||
public Position toPosition() {
|
||||
return new Position(CHUNK_SIZE_Z * getChunkX(), 0, CHUNK_SIZE_Z * getChunkZ());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets if this chunk will or had been loaded with a {@link ChunkGenerator}.
|
||||
* <p>
|
||||
* If false, the chunk will be entirely empty when loaded.
|
||||
*
|
||||
* @return true if this chunk is affected by a {@link ChunkGenerator}
|
||||
*/
|
||||
|
@ -165,7 +165,7 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta
|
||||
* <p>
|
||||
* WARNING: during unloading, all entities other than {@link Player} will be removed.
|
||||
* <p>
|
||||
* For {@link InstanceContainer} it is done during {@link InstanceContainer#tick(long)}
|
||||
* For {@link InstanceContainer} it is done during the next {@link InstanceContainer#tick(long)}.
|
||||
*
|
||||
* @param chunk the chunk to unload
|
||||
*/
|
||||
@ -661,8 +661,8 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta
|
||||
* Sends a {@link BlockActionPacket} for all the viewers of the specific position.
|
||||
*
|
||||
* @param blockPosition the block position
|
||||
* @param actionId
|
||||
* @param actionParam
|
||||
* @param actionId the action id, depends on the block
|
||||
* @param actionParam the action parameter, depends on the block
|
||||
* @see <a href="https://wiki.vg/Protocol#Block_Action">BlockActionPacket</a> for the action id & param
|
||||
*/
|
||||
public void sendBlockAction(@NotNull BlockPosition blockPosition, byte actionId, byte actionParam) {
|
||||
@ -1038,7 +1038,7 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the registered {@link ExplosionSupplier}, or null if none was provided
|
||||
* Gets the registered {@link ExplosionSupplier}, or null if none was provided.
|
||||
*
|
||||
* @return the instance explosion supplier, null if none was provided
|
||||
*/
|
||||
@ -1048,7 +1048,7 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the {@link ExplosionSupplier} to use in this instance
|
||||
* Registers the {@link ExplosionSupplier} to use in this instance.
|
||||
*
|
||||
* @param supplier the explosion supplier
|
||||
*/
|
||||
@ -1057,9 +1057,9 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the instance space
|
||||
* Gets the instance space.
|
||||
* <p>
|
||||
* Used by the pathfinder for entities
|
||||
* Used by the pathfinder for entities.
|
||||
*
|
||||
* @return the instance space
|
||||
*/
|
||||
|
@ -491,7 +491,7 @@ public class InstanceContainer extends Instance {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveChunksToStorage(Runnable callback) {
|
||||
public void saveChunksToStorage(@Nullable Runnable callback) {
|
||||
this.chunkLoader.saveChunks(chunks.values(), callback);
|
||||
}
|
||||
|
||||
@ -533,7 +533,7 @@ public class InstanceContainer extends Instance {
|
||||
chunkGenerator.fillBiomes(biomes, chunkX, chunkZ);
|
||||
}
|
||||
|
||||
final Chunk chunk = chunkSupplier.getChunk(this, biomes, chunkX, chunkZ);
|
||||
final Chunk chunk = chunkSupplier.getChunk(biomes, chunkX, chunkZ);
|
||||
Check.notNull(chunk, "Chunks supplied by a ChunkSupplier cannot be null.");
|
||||
|
||||
cacheChunk(chunk);
|
||||
@ -619,7 +619,7 @@ public class InstanceContainer extends Instance {
|
||||
/**
|
||||
* Copies all the chunks of this instance and create a new instance container with all of them.
|
||||
* <p>
|
||||
* Chunks are copied with {@link Chunk#copy(Instance, int, int)},
|
||||
* Chunks are copied with {@link Chunk#copy(int, int)},
|
||||
* {@link UUID} is randomized, {@link DimensionType} is passed over and the {@link StorageLocation} is null.
|
||||
*
|
||||
* @return an {@link InstanceContainer} with the exact same chunks as 'this'
|
||||
@ -630,15 +630,14 @@ public class InstanceContainer extends Instance {
|
||||
copiedInstance.srcInstance = this;
|
||||
copiedInstance.lastBlockChangeTime = lastBlockChangeTime;
|
||||
|
||||
ConcurrentHashMap<Long, Chunk> copiedChunks = copiedInstance.chunks;
|
||||
for (Map.Entry<Long, Chunk> entry : chunks.entrySet()) {
|
||||
final long index = entry.getKey();
|
||||
final Chunk chunk = entry.getValue();
|
||||
for (Chunk chunk : chunks.values()) {
|
||||
final int chunkX = chunk.getChunkX();
|
||||
final int chunkZ = chunk.getChunkZ();
|
||||
|
||||
final Chunk copiedChunk = chunk.copy(copiedInstance, chunk.getChunkX(), chunk.getChunkZ());
|
||||
final Chunk copiedChunk = chunk.copy(chunkX, chunkZ);
|
||||
|
||||
copiedChunks.put(index, copiedChunk);
|
||||
UPDATE_MANAGER.signalChunkLoad(copiedInstance, chunk.getChunkX(), chunk.getChunkZ());
|
||||
copiedInstance.cacheChunk(copiedChunk);
|
||||
UPDATE_MANAGER.signalChunkLoad(copiedInstance, chunkX, chunkZ);
|
||||
}
|
||||
|
||||
return copiedInstance;
|
||||
@ -677,10 +676,13 @@ public class InstanceContainer extends Instance {
|
||||
|
||||
/**
|
||||
* Adds a {@link Chunk} to the internal instance map.
|
||||
* <p>
|
||||
* WARNING: the chunk will not automatically be sent to players and
|
||||
* {@link net.minestom.server.UpdateManager#signalChunkLoad(Instance, int, int)} must be called manually.
|
||||
*
|
||||
* @param chunk the chunk to cache
|
||||
*/
|
||||
private void cacheChunk(Chunk chunk) {
|
||||
public void cacheChunk(@NotNull Chunk chunk) {
|
||||
final long index = ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ());
|
||||
this.chunks.put(index, chunk);
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import net.minestom.server.utils.callback.OptionalCallback;
|
||||
import net.minestom.server.utils.chunk.ChunkCallback;
|
||||
import net.minestom.server.utils.chunk.ChunkSupplier;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -37,10 +38,10 @@ public class MinestomBasicChunkLoader implements IChunkLoader {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveChunk(@NotNull Chunk chunk, Runnable callback) {
|
||||
public void saveChunk(@NotNull Chunk chunk, @Nullable Runnable callback) {
|
||||
final StorageLocation storageLocation = instanceContainer.getStorageLocation();
|
||||
if (storageLocation == null) {
|
||||
callback.run();
|
||||
OptionalCallback.execute(callback);
|
||||
LOGGER.warn("No storage location to save chunk!");
|
||||
return;
|
||||
}
|
||||
@ -65,7 +66,7 @@ public class MinestomBasicChunkLoader implements IChunkLoader {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean loadChunk(@NotNull Instance instance, int chunkX, int chunkZ, ChunkCallback callback) {
|
||||
public boolean loadChunk(@NotNull Instance instance, int chunkX, int chunkZ, @Nullable ChunkCallback callback) {
|
||||
final StorageLocation storageLocation = instanceContainer.getStorageLocation();
|
||||
final byte[] bytes = storageLocation == null ? null : storageLocation.get(getChunkKey(chunkX, chunkZ));
|
||||
|
||||
@ -76,7 +77,7 @@ public class MinestomBasicChunkLoader implements IChunkLoader {
|
||||
// Found, load from result bytes
|
||||
BinaryReader reader = new BinaryReader(bytes);
|
||||
// Create the chunk object using the instance's ChunkSupplier to support multiple implementations
|
||||
Chunk chunk = instanceContainer.getChunkSupplier().getChunk(instance, null, chunkX, chunkZ);
|
||||
Chunk chunk = instanceContainer.getChunkSupplier().getChunk(null, chunkX, chunkZ);
|
||||
// Execute the callback once all blocks are placed (allow for multithreaded implementations)
|
||||
chunk.readChunk(reader, callback);
|
||||
return true;
|
||||
|
@ -27,14 +27,15 @@ public class StaticChunk extends Chunk {
|
||||
|
||||
protected final BlockProvider blockProvider;
|
||||
|
||||
public StaticChunk(Instance instance, Biome[] biomes, int chunkX, int chunkZ, BlockProvider blockProvider) {
|
||||
super(instance, biomes, chunkX, chunkZ, false);
|
||||
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, Data data, boolean updatable) {
|
||||
public void UNSAFE_setBlock(int x, int y, int z, short blockStateId, short customBlockId, @Nullable Data data, boolean updatable) {
|
||||
//noop
|
||||
}
|
||||
|
||||
@ -113,8 +114,8 @@ public class StaticChunk extends Chunk {
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Chunk copy(@NotNull Instance instance, int chunkX, int chunkZ) {
|
||||
StaticChunk staticChunk = new StaticChunk(instance, biomes.clone(), chunkX, chunkZ, blockProvider);
|
||||
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) {
|
||||
|
@ -29,7 +29,7 @@ public class WorldBorder {
|
||||
private int warningTime;
|
||||
private int warningBlocks;
|
||||
|
||||
protected WorldBorder(Instance instance) {
|
||||
protected WorldBorder(@NotNull Instance instance) {
|
||||
this.instance = instance;
|
||||
|
||||
this.oldDiameter = Double.MAX_VALUE;
|
||||
@ -272,7 +272,7 @@ public class WorldBorder {
|
||||
*
|
||||
* @param worldBorderPacket the packet to send
|
||||
*/
|
||||
private void sendPacket(WorldBorderPacket worldBorderPacket) {
|
||||
private void sendPacket(@NotNull WorldBorderPacket worldBorderPacket) {
|
||||
PacketWriterUtils.writeAndSend(instance.getPlayers(), worldBorderPacket);
|
||||
}
|
||||
|
||||
|
@ -7,12 +7,12 @@ import org.jetbrains.annotations.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents an inventory where its items can be modified/retrieved.
|
||||
* Represents an inventory where items can be modified/retrieved.
|
||||
*/
|
||||
public interface InventoryModifier {
|
||||
|
||||
/**
|
||||
* Sets an {@link ItemStack} at the specified slot.
|
||||
* Sets an {@link ItemStack} at the specified slot and send relevant update to the viewer(s).
|
||||
*
|
||||
* @param slot the slot to set the item
|
||||
* @param itemStack the item to set
|
||||
@ -20,7 +20,9 @@ public interface InventoryModifier {
|
||||
void setItemStack(int slot, @NotNull ItemStack itemStack);
|
||||
|
||||
/**
|
||||
* Adds an {@link ItemStack} to the inventory.
|
||||
* Adds an {@link ItemStack} to the inventory and send relevant update to the viewer(s).
|
||||
* <p>
|
||||
* Even the item cannot be fully added, the amount of {@code itemStack} will be updated.
|
||||
*
|
||||
* @param itemStack the item to add
|
||||
* @return true if the item has been successfully fully added, false otherwise
|
||||
@ -28,7 +30,7 @@ public interface InventoryModifier {
|
||||
boolean addItemStack(@NotNull ItemStack itemStack);
|
||||
|
||||
/**
|
||||
* Clears the inventory.
|
||||
* Clears the inventory and send relevant update to the viewer(s).
|
||||
*/
|
||||
void clear();
|
||||
|
||||
@ -59,7 +61,7 @@ public interface InventoryModifier {
|
||||
/**
|
||||
* Gets all the {@link InventoryCondition} of this inventory.
|
||||
*
|
||||
* @return the inventory conditions
|
||||
* @return a modifiable {@link List} containing all the inventory conditions
|
||||
*/
|
||||
@NotNull
|
||||
List<InventoryCondition> getInventoryConditions();
|
||||
|
@ -199,13 +199,13 @@ public class InventoryClickProcessor {
|
||||
|
||||
clickResult = startCondition(clickResult, inventory, player, index, ClickType.SHIFT_CLICK, item, cursor);
|
||||
if (clickResult.isCancel())
|
||||
continue;
|
||||
break;
|
||||
|
||||
final int amount = itemRule.getAmount(item);
|
||||
if (!clickedRule.canApply(clicked, amount + 1))
|
||||
continue;
|
||||
|
||||
int totalAmount = clickedRule.getAmount(resultClicked) + amount;
|
||||
final int totalAmount = clickedRule.getAmount(resultClicked) + amount;
|
||||
if (!clickedRule.canApply(clicked, totalAmount)) {
|
||||
item = itemRule.apply(item, itemRule.getMaxSize());
|
||||
itemSetter.accept(index, item);
|
||||
@ -230,7 +230,7 @@ public class InventoryClickProcessor {
|
||||
|
||||
clickResult = startCondition(clickResult, inventory, player, index, ClickType.SHIFT_CLICK, item, cursor);
|
||||
if (clickResult.isCancel())
|
||||
continue;
|
||||
break;
|
||||
|
||||
// Switch
|
||||
itemSetter.accept(index, resultClicked);
|
||||
@ -278,12 +278,12 @@ public class InventoryClickProcessor {
|
||||
int finalCursorAmount = cursorAmount;
|
||||
|
||||
for (Integer s : slots) {
|
||||
ItemStack draggedItem = cursor.copy();
|
||||
final ItemStack draggedItem = cursor.copy();
|
||||
ItemStack slotItem = itemGetter.apply(s);
|
||||
|
||||
clickResult = startCondition(clickResult, inventory, player, s, ClickType.DRAGGING, slotItem, cursor);
|
||||
if (clickResult.isCancel())
|
||||
continue;
|
||||
break;
|
||||
|
||||
final int maxSize = stackingRule.getMaxSize();
|
||||
if (stackingRule.canBeStacked(draggedItem, slotItem)) {
|
||||
@ -323,7 +323,7 @@ public class InventoryClickProcessor {
|
||||
|
||||
clickResult = startCondition(clickResult, inventory, player, s, ClickType.DRAGGING, slotItem, cursor);
|
||||
if (clickResult.isCancel())
|
||||
continue;
|
||||
break;
|
||||
|
||||
if (stackingRule.canBeStacked(draggedItem, slotItem)) {
|
||||
final int amount = slotItem.getAmount() + 1;
|
||||
@ -399,7 +399,7 @@ public class InventoryClickProcessor {
|
||||
if (cursorRule.canBeStacked(cursor, item)) {
|
||||
clickResult = startCondition(clickResult, inventory, player, index, ClickType.DOUBLE_CLICK, item, cursor);
|
||||
if (clickResult.isCancel())
|
||||
continue;
|
||||
break;
|
||||
|
||||
final int totalAmount = amount + cursorRule.getAmount(item);
|
||||
if (!cursorRule.canApply(cursor, totalAmount)) {
|
||||
|
@ -27,7 +27,7 @@ public class FurnaceInventory extends Inventory {
|
||||
/**
|
||||
* Represents the amount of tick until the fire icon come empty.
|
||||
*
|
||||
* @param remainingFuelTick
|
||||
* @param remainingFuelTick the amount of tick until the fire icon is empty
|
||||
*/
|
||||
public void setRemainingFuelTick(short remainingFuelTick) {
|
||||
this.remainingFuelTick = remainingFuelTick;
|
||||
|
@ -3,6 +3,7 @@ package net.minestom.server.item;
|
||||
import net.minestom.server.chat.ColoredText;
|
||||
import net.minestom.server.data.Data;
|
||||
import net.minestom.server.data.DataContainer;
|
||||
import net.minestom.server.data.NbtDataImpl;
|
||||
import net.minestom.server.entity.ItemEntity;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.inventory.Inventory;
|
||||
@ -525,7 +526,8 @@ public class ItemStack implements DataContainer {
|
||||
!attributes.isEmpty() ||
|
||||
hideFlag != 0 ||
|
||||
customModelData != 0 ||
|
||||
(itemMeta != null && itemMeta.hasNbt());
|
||||
(itemMeta != null && itemMeta.hasNbt()) ||
|
||||
(data instanceof NbtDataImpl && !data.isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -557,16 +559,25 @@ public class ItemStack implements DataContainer {
|
||||
final Data data = getData();
|
||||
if (data != null)
|
||||
itemStack.setData(data.copy());
|
||||
|
||||
return itemStack;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Data getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the data of this item.
|
||||
* <p>
|
||||
* It is recommended to use {@link NbtDataImpl} if you want the data to be passed to the client.
|
||||
*
|
||||
* @param data the new {@link Data} of this container, null to remove it
|
||||
*/
|
||||
@Override
|
||||
public void setData(Data data) {
|
||||
public void setData(@Nullable Data data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@ -676,6 +687,13 @@ public class ItemStack implements DataContainer {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link NBTCompound} containing the data of this item.
|
||||
* <p>
|
||||
* WARNING: modifying the returned nbt will not affect the item.
|
||||
*
|
||||
* @return this item nbt
|
||||
*/
|
||||
@NotNull
|
||||
public NBTCompound toNBT() {
|
||||
NBTCompound compound = new NBTCompound()
|
||||
|
@ -47,7 +47,7 @@ public class PacketController {
|
||||
/**
|
||||
* Changes the packet listener, setting it to null cancel the listener.
|
||||
* <p>
|
||||
* WARNING: this will overwrite the default minestom listener.
|
||||
* 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
|
||||
*/
|
||||
|
@ -197,7 +197,6 @@ public enum MapColors {
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
System.out.println("done mapping."); // todo: remove, debug only
|
||||
}
|
||||
|
||||
public static PreciseMapColor closestColor(int argb) {
|
||||
|
@ -5,8 +5,10 @@ import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.entity.fakeplayer.FakePlayer;
|
||||
import net.minestom.server.listener.manager.PacketConsumer;
|
||||
import net.minestom.server.network.packet.client.login.LoginStartPacket;
|
||||
import net.minestom.server.network.packet.server.login.LoginSuccessPacket;
|
||||
import net.minestom.server.network.packet.server.play.ChatMessagePacket;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.utils.callback.validator.PlayerValidator;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@ -14,7 +16,6 @@ import java.util.*;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Manages the connected clients.
|
||||
@ -93,7 +94,7 @@ public final class ConnectionManager {
|
||||
* @param jsonMessage the message to send, probably a {@link net.minestom.server.chat.ColoredText} or {@link net.minestom.server.chat.RichMessage}
|
||||
* @param condition the condition to receive the message
|
||||
*/
|
||||
public void broadcastMessage(@NotNull JsonMessage jsonMessage, @Nullable Function<Player, Boolean> condition) {
|
||||
public void broadcastMessage(@NotNull JsonMessage jsonMessage, @Nullable PlayerValidator condition) {
|
||||
final Collection<Player> recipients = getRecipients(condition);
|
||||
|
||||
if (!recipients.isEmpty()) {
|
||||
@ -117,7 +118,7 @@ public final class ConnectionManager {
|
||||
PacketWriterUtils.writeAndSend(recipients, chatMessagePacket);
|
||||
}
|
||||
|
||||
private Collection<Player> getRecipients(@Nullable Function<Player, Boolean> condition) {
|
||||
private Collection<Player> getRecipients(@Nullable PlayerValidator condition) {
|
||||
Collection<Player> recipients;
|
||||
|
||||
// Get the recipients
|
||||
@ -126,7 +127,7 @@ public final class ConnectionManager {
|
||||
} else {
|
||||
recipients = new ArrayList<>();
|
||||
getOnlinePlayers().forEach(player -> {
|
||||
final boolean result = condition.apply(player);
|
||||
final boolean result = condition.isValid(player);
|
||||
if (result)
|
||||
recipients.add(player);
|
||||
});
|
||||
@ -259,6 +260,7 @@ public final class ConnectionManager {
|
||||
* Used during disconnection, you shouldn't have to do it manually.
|
||||
*
|
||||
* @param connection the player connection
|
||||
* @see PlayerConnection#disconnect() to properly disconnect a player
|
||||
*/
|
||||
public void removePlayer(@NotNull PlayerConnection connection) {
|
||||
final Player player = this.connectionPlayerMap.get(connection);
|
||||
@ -268,4 +270,19 @@ public final class ConnectionManager {
|
||||
this.players.remove(player);
|
||||
this.connectionPlayerMap.remove(connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a {@link LoginSuccessPacket} and change the connection state to {@link ConnectionState#PLAY}.
|
||||
*
|
||||
* @param connection the player connection
|
||||
* @param uuid the uuid of the player
|
||||
* @param username the username of the player
|
||||
*/
|
||||
public void startPlayState(@NotNull PlayerConnection connection, @NotNull UUID uuid, @NotNull String username) {
|
||||
LoginSuccessPacket loginSuccessPacket = new LoginSuccessPacket(uuid, username);
|
||||
connection.sendPacket(loginSuccessPacket);
|
||||
|
||||
connection.setConnectionState(ConnectionState.PLAY);
|
||||
createPlayer(uuid, username, connection);
|
||||
}
|
||||
}
|
||||
|
@ -14,31 +14,34 @@ import net.minestom.server.network.packet.client.handshake.HandshakePacket;
|
||||
import net.minestom.server.network.player.NettyPlayerConnection;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.utils.binary.BinaryReader;
|
||||
import net.minestom.server.utils.binary.Readable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class PacketProcessor {
|
||||
public final class PacketProcessor {
|
||||
|
||||
private final static Logger LOGGER = LoggerFactory.getLogger(PacketProcessor.class);
|
||||
|
||||
private final Map<ChannelHandlerContext, PlayerConnection> connectionPlayerConnectionMap = new ConcurrentHashMap<>();
|
||||
|
||||
// Protocols
|
||||
// Protocols state
|
||||
private final ClientStatusPacketsHandler statusPacketsHandler;
|
||||
private final ClientLoginPacketsHandler loginPacketsHandler;
|
||||
private final ClientPlayPacketsHandler playPacketsHandler;
|
||||
|
||||
public PacketProcessor() {
|
||||
|
||||
this.statusPacketsHandler = new ClientStatusPacketsHandler();
|
||||
this.loginPacketsHandler = new ClientLoginPacketsHandler();
|
||||
this.playPacketsHandler = new ClientPlayPacketsHandler();
|
||||
}
|
||||
|
||||
private List<Integer> printBlackList = Arrays.asList(17, 18, 19);
|
||||
|
||||
public void process(ChannelHandlerContext channel, InboundPacket packet) {
|
||||
public void process(@NotNull ChannelHandlerContext channel, @NotNull InboundPacket packet) {
|
||||
// Create the netty player connection object if not existing
|
||||
PlayerConnection playerConnection = connectionPlayerConnectionMap.computeIfAbsent(
|
||||
channel, c -> new NettyPlayerConnection((SocketChannel) channel.channel())
|
||||
);
|
||||
@ -48,17 +51,13 @@ public class PacketProcessor {
|
||||
|
||||
final ConnectionState connectionState = playerConnection.getConnectionState();
|
||||
|
||||
//if (!printBlackList.contains(id)) {
|
||||
//System.out.println("RECEIVED ID: 0x" + Integer.toHexString(id) + " State: " + connectionState);
|
||||
//}
|
||||
|
||||
BinaryReader binaryReader = new BinaryReader(packet.body);
|
||||
|
||||
if (connectionState == ConnectionState.UNKNOWN) {
|
||||
// Should be handshake packet
|
||||
if (packet.packetId == 0) {
|
||||
HandshakePacket handshakePacket = new HandshakePacket();
|
||||
handshakePacket.read(binaryReader);
|
||||
safeRead(playerConnection, handshakePacket, binaryReader);
|
||||
handshakePacket.process(playerConnection);
|
||||
}
|
||||
return;
|
||||
@ -68,22 +67,29 @@ public class PacketProcessor {
|
||||
case PLAY:
|
||||
final Player player = playerConnection.getPlayer();
|
||||
ClientPlayPacket playPacket = (ClientPlayPacket) playPacketsHandler.getPacketInstance(packet.packetId);
|
||||
playPacket.read(binaryReader);
|
||||
safeRead(playerConnection, playPacket, binaryReader);
|
||||
player.addPacketToQueue(playPacket);
|
||||
break;
|
||||
case LOGIN:
|
||||
final ClientPreplayPacket loginPacket = (ClientPreplayPacket) loginPacketsHandler.getPacketInstance(packet.packetId);
|
||||
loginPacket.read(binaryReader);
|
||||
safeRead(playerConnection, loginPacket, binaryReader);
|
||||
loginPacket.process(playerConnection);
|
||||
break;
|
||||
case STATUS:
|
||||
final ClientPreplayPacket statusPacket = (ClientPreplayPacket) statusPacketsHandler.getPacketInstance(packet.packetId);
|
||||
statusPacket.read(binaryReader);
|
||||
safeRead(playerConnection, statusPacket, binaryReader);
|
||||
statusPacket.process(playerConnection);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a player connection from its channel.
|
||||
*
|
||||
* @param channel the connection channel
|
||||
* @return the connection of this channel, null if not found
|
||||
*/
|
||||
@Nullable
|
||||
public PlayerConnection getPlayerConnection(ChannelHandlerContext channel) {
|
||||
return connectionPlayerConnectionMap.get(channel);
|
||||
}
|
||||
@ -91,4 +97,21 @@ public class PacketProcessor {
|
||||
public void removePlayerConnection(ChannelHandlerContext channel) {
|
||||
connectionPlayerConnectionMap.remove(channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link Readable#read(BinaryReader)} and catch all the exceptions to be printed using the packet processor logger.
|
||||
*
|
||||
* @param connection the connection who sent the packet
|
||||
* @param readable the readable interface
|
||||
* @param reader the buffer containing the packet
|
||||
*/
|
||||
private void safeRead(@NotNull PlayerConnection connection, @NotNull Readable readable, @NotNull BinaryReader reader) {
|
||||
try {
|
||||
readable.read(reader);
|
||||
} catch (Exception e) {
|
||||
final Player player = connection.getPlayer();
|
||||
final String username = player != null ? player.getUsername() : "null";
|
||||
LOGGER.warn("Connection " + connection.getRemoteAddress() + " (" + username + ") sent an unexpected packet.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -61,6 +61,7 @@ public class ClientChannel extends SimpleChannelInboundHandler<InboundPacket> {
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
log.info(cause.getMessage());
|
||||
cause.printStackTrace();
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
package net.minestom.server.network.packet.client;
|
||||
|
||||
import net.minestom.server.utils.binary.BinaryReader;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import net.minestom.server.utils.binary.Readable;
|
||||
|
||||
public interface ClientPacket {
|
||||
|
||||
void read(@NotNull BinaryReader reader);
|
||||
/**
|
||||
* Represents a packet received from a client.
|
||||
*/
|
||||
public interface ClientPacket extends Readable {
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.minestom.server.network.packet.client.handler;
|
||||
|
||||
import net.minestom.server.network.packet.client.login.EncryptionResponsePacket;
|
||||
import net.minestom.server.network.packet.client.login.LoginPluginResponsePacket;
|
||||
import net.minestom.server.network.packet.client.login.LoginStartPacket;
|
||||
|
||||
public class ClientLoginPacketsHandler extends ClientPacketsHandler {
|
||||
@ -8,6 +9,7 @@ public class ClientLoginPacketsHandler extends ClientPacketsHandler {
|
||||
public ClientLoginPacketsHandler() {
|
||||
register(0, LoginStartPacket::new);
|
||||
register(1, EncryptionResponsePacket::new);
|
||||
register(2, LoginPluginResponsePacket::new);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.minestom.server.network.packet.client.handler;
|
||||
|
||||
import net.minestom.server.network.packet.client.ClientPacket;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@ -9,17 +10,30 @@ public class ClientPacketsHandler {
|
||||
// Max packet id
|
||||
private static final int SIZE = 0x30;
|
||||
|
||||
private final Supplier<ClientPacket>[] supplierAccesses = new Supplier[SIZE];
|
||||
private final ClientPacketSupplier[] supplierAccesses = new ClientPacketSupplier[SIZE];
|
||||
|
||||
public void register(int id, Supplier<ClientPacket> packetSupplier) {
|
||||
supplierAccesses[id] = packetSupplier;
|
||||
/**
|
||||
* Registers a client packet which can be retrieved later using {@link #getPacketInstance(int)}.
|
||||
*
|
||||
* @param id the packet id
|
||||
* @param packetSupplier the supplier of the packet
|
||||
*/
|
||||
public void register(int id, @NotNull ClientPacketSupplier packetSupplier) {
|
||||
this.supplierAccesses[id] = packetSupplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a {@link net.minestom.server.network.packet.client.ClientPlayPacket} from its id.
|
||||
*
|
||||
* @param id the packet id
|
||||
* @return the associated client packet
|
||||
* @throws IllegalStateException if {@code id} is not a valid packet id, or unregistered
|
||||
*/
|
||||
public ClientPacket getPacketInstance(int id) {
|
||||
if (id > SIZE)
|
||||
throw new IllegalStateException("Packet ID 0x" + Integer.toHexString(id) + " has been tried to be parsed, debug needed");
|
||||
|
||||
Supplier<ClientPacket> supplier = supplierAccesses[id];
|
||||
ClientPacketSupplier supplier = supplierAccesses[id];
|
||||
if (supplierAccesses[id] == null)
|
||||
throw new IllegalStateException("Packet id 0x" + Integer.toHexString(id) + " isn't registered!");
|
||||
|
||||
@ -28,4 +42,10 @@ public class ClientPacketsHandler {
|
||||
return supplier.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient interface to supply a {@link ClientPacket}.
|
||||
*/
|
||||
protected interface ClientPacketSupplier extends Supplier<ClientPacket> {
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,14 +3,17 @@ package net.minestom.server.network.packet.client.handshake;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.chat.ChatColor;
|
||||
import net.minestom.server.chat.ColoredText;
|
||||
import net.minestom.server.extras.bungee.BungeeCordProxy;
|
||||
import net.minestom.server.network.ConnectionState;
|
||||
import net.minestom.server.network.packet.client.ClientPreplayPacket;
|
||||
import net.minestom.server.network.packet.server.login.LoginDisconnect;
|
||||
import net.minestom.server.network.packet.server.login.LoginDisconnectPacket;
|
||||
import net.minestom.server.network.player.NettyPlayerConnection;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.utils.binary.BinaryReader;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
|
||||
public class HandshakePacket implements ClientPreplayPacket {
|
||||
|
||||
/**
|
||||
@ -18,6 +21,8 @@ public class HandshakePacket implements ClientPreplayPacket {
|
||||
*/
|
||||
private static final ColoredText INVALID_VERSION_TEXT = ColoredText.of(ChatColor.RED, "Invalid Version, please use " + MinecraftServer.VERSION_NAME);
|
||||
|
||||
private static final ColoredText INVALID_BUNGEE_FORWARDING = ColoredText.of(ChatColor.RED, "If you wish to use IP forwarding, please enable it in your BungeeCord config as well!");
|
||||
|
||||
private int protocolVersion;
|
||||
private String serverAddress;
|
||||
private int serverPort;
|
||||
@ -33,6 +38,29 @@ public class HandshakePacket implements ClientPreplayPacket {
|
||||
|
||||
@Override
|
||||
public void process(@NotNull PlayerConnection connection) {
|
||||
|
||||
if (BungeeCordProxy.isEnabled() && connection instanceof NettyPlayerConnection) {
|
||||
NettyPlayerConnection nettyPlayerConnection = (NettyPlayerConnection) connection;
|
||||
|
||||
if (serverAddress != null) {
|
||||
final String[] split = serverAddress.split("\00");
|
||||
|
||||
if (split.length == 3 || split.length == 4) {
|
||||
this.serverAddress = split[0];
|
||||
|
||||
final SocketAddress socketAddress = new java.net.InetSocketAddress(split[1], ((java.net.InetSocketAddress) connection.getRemoteAddress()).getPort());
|
||||
nettyPlayerConnection.setRemoteAddress(socketAddress);
|
||||
} else {
|
||||
nettyPlayerConnection.sendPacket(new LoginDisconnectPacket(INVALID_BUNGEE_FORWARDING));
|
||||
nettyPlayerConnection.disconnect();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Happen when a client ping the server, ignore
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
switch (nextState) {
|
||||
case 1:
|
||||
connection.setConnectionState(ConnectionState.STATUS);
|
||||
@ -47,7 +75,7 @@ public class HandshakePacket implements ClientPreplayPacket {
|
||||
}
|
||||
} else {
|
||||
// Incorrect client version
|
||||
connection.sendPacket(new LoginDisconnect(INVALID_VERSION_TEXT.toString()));
|
||||
connection.sendPacket(new LoginDisconnectPacket(INVALID_VERSION_TEXT.toString()));
|
||||
connection.disconnect();
|
||||
}
|
||||
break;
|
||||
|
@ -5,9 +5,7 @@ import com.mojang.authlib.exceptions.AuthenticationUnavailableException;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.data.type.array.ByteArrayData;
|
||||
import net.minestom.server.extras.mojangAuth.MojangCrypt;
|
||||
import net.minestom.server.network.ConnectionState;
|
||||
import net.minestom.server.network.packet.client.ClientPreplayPacket;
|
||||
import net.minestom.server.network.packet.server.login.LoginSuccessPacket;
|
||||
import net.minestom.server.network.player.NettyPlayerConnection;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.utils.binary.BinaryReader;
|
||||
@ -32,17 +30,18 @@ public class EncryptionResponsePacket implements ClientPreplayPacket {
|
||||
if (!(connection instanceof NettyPlayerConnection)) {
|
||||
return;
|
||||
}
|
||||
final NettyPlayerConnection nettyConnection = (NettyPlayerConnection) connection;
|
||||
|
||||
new Thread(THREAD_NAME + " #" + UNIQUE_THREAD_ID.incrementAndGet()) {
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
if (!Arrays.equals(connection.getNonce(), getNonce())) {
|
||||
MinecraftServer.getLOGGER().error(connection.getLoginUsername() + " tried to login with an invalid nonce!");
|
||||
final String loginUsername = nettyConnection.getLoginUsername();
|
||||
if (!Arrays.equals(nettyConnection.getNonce(), getNonce())) {
|
||||
MinecraftServer.getLOGGER().error(loginUsername + " tried to login with an invalid nonce!");
|
||||
return;
|
||||
}
|
||||
if (!connection.getLoginUsername().isEmpty()) {
|
||||
final NettyPlayerConnection nettyConnection = (NettyPlayerConnection) connection;
|
||||
if (!loginUsername.isEmpty()) {
|
||||
|
||||
final byte[] digestedData = MojangCrypt.digestData("", MinecraftServer.getKeyPair().getPublic(), getSecretKey());
|
||||
|
||||
@ -54,19 +53,11 @@ public class EncryptionResponsePacket implements ClientPreplayPacket {
|
||||
}
|
||||
|
||||
final String string3 = new BigInteger(digestedData).toString(16);
|
||||
final GameProfile gameProfile = MinecraftServer.getSessionService().hasJoinedServer(new GameProfile(null, connection.getLoginUsername()), string3);
|
||||
final GameProfile gameProfile = MinecraftServer.getSessionService().hasJoinedServer(new GameProfile(null, loginUsername), string3);
|
||||
nettyConnection.setEncryptionKey(getSecretKey());
|
||||
final int threshold = MinecraftServer.getCompressionThreshold();
|
||||
|
||||
if (threshold > 0) {
|
||||
nettyConnection.enableCompression(threshold);
|
||||
}
|
||||
|
||||
LoginSuccessPacket loginSuccessPacket = new LoginSuccessPacket(gameProfile.getId(), gameProfile.getName());
|
||||
connection.sendPacket(loginSuccessPacket);
|
||||
MinecraftServer.getLOGGER().info("UUID of player {} is {}", connection.getLoginUsername(), gameProfile.getId());
|
||||
connection.setConnectionState(ConnectionState.PLAY);
|
||||
CONNECTION_MANAGER.createPlayer(gameProfile.getId(), gameProfile.getName(), connection);
|
||||
MinecraftServer.getLOGGER().info("UUID of player {} is {}", loginUsername, gameProfile.getId());
|
||||
CONNECTION_MANAGER.startPlayState(connection, gameProfile.getId(), gameProfile.getName());
|
||||
}
|
||||
} catch (AuthenticationUnavailableException e) {
|
||||
e.printStackTrace();
|
||||
|
@ -0,0 +1,83 @@
|
||||
package net.minestom.server.network.packet.client.login;
|
||||
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.chat.ChatColor;
|
||||
import net.minestom.server.chat.ColoredText;
|
||||
import net.minestom.server.extras.velocity.VelocityProxy;
|
||||
import net.minestom.server.network.ConnectionManager;
|
||||
import net.minestom.server.network.packet.client.ClientPreplayPacket;
|
||||
import net.minestom.server.network.packet.server.login.LoginDisconnectPacket;
|
||||
import net.minestom.server.network.player.NettyPlayerConnection;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.utils.binary.BinaryReader;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.UUID;
|
||||
|
||||
public class LoginPluginResponsePacket implements ClientPreplayPacket {
|
||||
|
||||
private final static ConnectionManager CONNECTION_MANAGER = MinecraftServer.getConnectionManager();
|
||||
|
||||
public static final ColoredText INVALID_PROXY_RESPONSE = ColoredText.of(ChatColor.RED, "Invalid proxy response!");
|
||||
|
||||
public int messageId;
|
||||
public boolean successful;
|
||||
public byte[] data;
|
||||
|
||||
@Override
|
||||
public void process(@NotNull PlayerConnection connection) {
|
||||
|
||||
// Proxy support
|
||||
if (connection instanceof NettyPlayerConnection) {
|
||||
final NettyPlayerConnection nettyPlayerConnection = (NettyPlayerConnection) connection;
|
||||
final String channel = nettyPlayerConnection.getPluginRequestChannel(messageId);
|
||||
|
||||
if (channel != null) {
|
||||
boolean success = false;
|
||||
SocketAddress socketAddress = null;
|
||||
|
||||
// Velocity
|
||||
if (VelocityProxy.isEnabled() && channel.equals(VelocityProxy.PLAYER_INFO_CHANNEL)) {
|
||||
if (data != null) {
|
||||
BinaryReader reader = new BinaryReader(data);
|
||||
success = VelocityProxy.checkIntegrity(reader);
|
||||
if (success) {
|
||||
// Get the real connection address
|
||||
final InetAddress address = VelocityProxy.readAddress(reader);
|
||||
final int port = ((java.net.InetSocketAddress) connection.getRemoteAddress()).getPort();
|
||||
socketAddress = new InetSocketAddress(address, port);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (success) {
|
||||
if (socketAddress != null) {
|
||||
nettyPlayerConnection.setRemoteAddress(socketAddress);
|
||||
}
|
||||
|
||||
// Proxy usage always mean that the server is in offline mode
|
||||
final String username = nettyPlayerConnection.getLoginUsername();
|
||||
final UUID playerUuid = CONNECTION_MANAGER.getPlayerConnectionUuid(connection, username);
|
||||
|
||||
CONNECTION_MANAGER.startPlayState(connection, playerUuid, username);
|
||||
} else {
|
||||
LoginDisconnectPacket disconnectPacket = new LoginDisconnectPacket(INVALID_PROXY_RESPONSE);
|
||||
nettyPlayerConnection.sendPacket(disconnectPacket);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(@NotNull BinaryReader reader) {
|
||||
this.messageId = reader.readVarInt();
|
||||
this.successful = reader.readBoolean();
|
||||
if (successful) {
|
||||
this.data = reader.getRemainingBytes();
|
||||
}
|
||||
}
|
||||
}
|
@ -4,52 +4,85 @@ import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.chat.ChatColor;
|
||||
import net.minestom.server.chat.ColoredText;
|
||||
import net.minestom.server.extras.MojangAuth;
|
||||
import net.minestom.server.extras.velocity.VelocityProxy;
|
||||
import net.minestom.server.network.ConnectionState;
|
||||
import net.minestom.server.network.packet.client.ClientPreplayPacket;
|
||||
import net.minestom.server.network.packet.server.login.EncryptionRequestPacket;
|
||||
import net.minestom.server.network.packet.server.login.LoginDisconnect;
|
||||
import net.minestom.server.network.packet.server.login.LoginSuccessPacket;
|
||||
import net.minestom.server.network.packet.server.login.LoginDisconnectPacket;
|
||||
import net.minestom.server.network.packet.server.login.LoginPluginRequestPacket;
|
||||
import net.minestom.server.network.player.NettyPlayerConnection;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.utils.binary.BinaryReader;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class LoginStartPacket implements ClientPreplayPacket {
|
||||
|
||||
private static final String ALREADY_CONNECTED_JSON =
|
||||
ColoredText.of(ChatColor.RED, "You are already on this server").toString();
|
||||
private static final ColoredText ALREADY_CONNECTED_JSON = ColoredText.of(ChatColor.RED, "You are already on this server");
|
||||
|
||||
public String username;
|
||||
|
||||
@Override
|
||||
public void process(@NotNull PlayerConnection connection) {
|
||||
if (MojangAuth.isUsingMojangAuth()) {
|
||||
|
||||
// Cache the login username and start compression if enabled
|
||||
if (connection instanceof NettyPlayerConnection) {
|
||||
NettyPlayerConnection nettyPlayerConnection = (NettyPlayerConnection) connection;
|
||||
nettyPlayerConnection.UNSAFE_setLoginUsername(username);
|
||||
|
||||
// Compression
|
||||
final int threshold = MinecraftServer.getCompressionThreshold();
|
||||
if (threshold > 0) {
|
||||
nettyPlayerConnection.enableCompression(threshold);
|
||||
}
|
||||
}
|
||||
|
||||
// Proxy support (only for netty clients)
|
||||
if (connection instanceof NettyPlayerConnection) {
|
||||
final NettyPlayerConnection nettyPlayerConnection = (NettyPlayerConnection) connection;
|
||||
|
||||
{
|
||||
// Velocity support
|
||||
if (VelocityProxy.isEnabled()) {
|
||||
|
||||
final int messageId = ThreadLocalRandom.current().nextInt();
|
||||
final String channel = VelocityProxy.PLAYER_INFO_CHANNEL;
|
||||
|
||||
// Important in order to retrieve the channel in the response packet
|
||||
nettyPlayerConnection.addPluginRequestEntry(messageId, channel);
|
||||
|
||||
LoginPluginRequestPacket loginPluginRequestPacket = new LoginPluginRequestPacket();
|
||||
loginPluginRequestPacket.messageId = messageId;
|
||||
loginPluginRequestPacket.channel = channel;
|
||||
loginPluginRequestPacket.data = null;
|
||||
connection.sendPacket(loginPluginRequestPacket);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (MojangAuth.isUsingMojangAuth() && connection instanceof NettyPlayerConnection) {
|
||||
// Mojang auth
|
||||
if (CONNECTION_MANAGER.getPlayer(username) != null) {
|
||||
connection.sendPacket(new LoginDisconnect(ALREADY_CONNECTED_JSON));
|
||||
connection.sendPacket(new LoginDisconnectPacket(ALREADY_CONNECTED_JSON));
|
||||
connection.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
connection.setConnectionState(ConnectionState.LOGIN);
|
||||
connection.setLoginUsername(username);
|
||||
EncryptionRequestPacket encryptionRequestPacket = new EncryptionRequestPacket(connection);
|
||||
connection.sendPacket(encryptionRequestPacket);
|
||||
final NettyPlayerConnection nettyPlayerConnection = (NettyPlayerConnection) connection;
|
||||
|
||||
nettyPlayerConnection.setConnectionState(ConnectionState.LOGIN);
|
||||
EncryptionRequestPacket encryptionRequestPacket = new EncryptionRequestPacket(nettyPlayerConnection);
|
||||
nettyPlayerConnection.sendPacket(encryptionRequestPacket);
|
||||
} else {
|
||||
// Offline
|
||||
final UUID playerUuid = CONNECTION_MANAGER.getPlayerConnectionUuid(connection, username);
|
||||
|
||||
final int threshold = MinecraftServer.getCompressionThreshold();
|
||||
|
||||
if (threshold > 0 && connection instanceof NettyPlayerConnection) {
|
||||
((NettyPlayerConnection) connection).enableCompression(threshold);
|
||||
}
|
||||
|
||||
LoginSuccessPacket successPacket = new LoginSuccessPacket(playerUuid, username);
|
||||
connection.sendPacket(successPacket);
|
||||
|
||||
connection.setConnectionState(ConnectionState.PLAY);
|
||||
CONNECTION_MANAGER.createPlayer(playerUuid, username, connection);
|
||||
CONNECTION_MANAGER.startPlayState(connection, playerUuid, username);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ public class StatusRequestPacket implements ClientPreplayPacket {
|
||||
responseData.setMaxPlayer(0);
|
||||
responseData.setOnline(0);
|
||||
responseData.setDescription("Minestom Server");
|
||||
responseData.setFavicon("data:image/png;base64,<data>");
|
||||
responseData.setFavicon("");
|
||||
|
||||
if (consumer != null)
|
||||
consumer.accept(connection, responseData);
|
||||
|
@ -2,6 +2,12 @@ package net.minestom.server.network.packet.server;
|
||||
|
||||
public class ServerPacketIdentifier {
|
||||
|
||||
public static final int LOGIN_DISCONNECT = 0x00;
|
||||
public static final int LOGIN_ENCRYPTION_REQUEST = 0x01;
|
||||
public static final int LOGIN_SUCCESS = 0x02;
|
||||
public static final int LOGIN_SET_COMPRESSION = 0x03;
|
||||
public static final int LOGIN_PLUGIN_REQUEST = 0x04;
|
||||
|
||||
public static final int SPAWN_ENTITY = 0x00;
|
||||
public static final int SPAWN_EXPERIENCE_ORB = 0x01;
|
||||
public static final int SPAWN_LIVING_ENTITY = 0x02;
|
||||
|
@ -3,7 +3,8 @@ 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.network.packet.server.ServerPacket;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.network.packet.server.ServerPacketIdentifier;
|
||||
import net.minestom.server.network.player.NettyPlayerConnection;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@ -14,7 +15,7 @@ public class EncryptionRequestPacket implements ServerPacket {
|
||||
public byte[] publicKey;
|
||||
public byte[] nonce = new byte[4];
|
||||
|
||||
public EncryptionRequestPacket(PlayerConnection connection) {
|
||||
public EncryptionRequestPacket(NettyPlayerConnection connection) {
|
||||
ThreadLocalRandom.current().nextBytes(nonce);
|
||||
connection.setNonce(nonce);
|
||||
}
|
||||
@ -29,6 +30,6 @@ public class EncryptionRequestPacket implements ServerPacket {
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return 0x01;
|
||||
return ServerPacketIdentifier.LOGIN_ENCRYPTION_REQUEST;
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +0,0 @@
|
||||
package net.minestom.server.network.packet.server.login;
|
||||
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class LoginDisconnect implements ServerPacket {
|
||||
|
||||
private String kickMessage;
|
||||
|
||||
public LoginDisconnect(String kickMessage) {
|
||||
this.kickMessage = kickMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(@NotNull BinaryWriter writer) {
|
||||
writer.writeSizedString(kickMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package net.minestom.server.network.packet.server.login;
|
||||
|
||||
import net.minestom.server.chat.JsonMessage;
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import net.minestom.server.network.packet.server.ServerPacketIdentifier;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class LoginDisconnectPacket implements ServerPacket {
|
||||
|
||||
private String kickMessage; // JSON text
|
||||
|
||||
public LoginDisconnectPacket(@NotNull String kickMessage) {
|
||||
this.kickMessage = kickMessage;
|
||||
}
|
||||
|
||||
public LoginDisconnectPacket(@NotNull JsonMessage jsonKickMessage) {
|
||||
this(jsonKickMessage.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(@NotNull BinaryWriter writer) {
|
||||
writer.writeSizedString(kickMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return ServerPacketIdentifier.LOGIN_DISCONNECT;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package net.minestom.server.network.packet.server.login;
|
||||
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import net.minestom.server.network.packet.server.ServerPacketIdentifier;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class LoginPluginRequestPacket implements ServerPacket {
|
||||
|
||||
public int messageId;
|
||||
public String channel;
|
||||
public byte[] data;
|
||||
|
||||
@Override
|
||||
public void write(@NotNull BinaryWriter writer) {
|
||||
writer.writeVarInt(messageId);
|
||||
writer.writeSizedString(channel);
|
||||
if (data != null && data.length > 0) {
|
||||
writer.writeBytes(data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return ServerPacketIdentifier.LOGIN_PLUGIN_REQUEST;
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package net.minestom.server.network.packet.server.login;
|
||||
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import net.minestom.server.network.packet.server.ServerPacketIdentifier;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@ -24,6 +25,6 @@ public class LoginSuccessPacket implements ServerPacket {
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return 0x02;
|
||||
return ServerPacketIdentifier.LOGIN_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.minestom.server.network.packet.server.login;
|
||||
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import net.minestom.server.network.packet.server.ServerPacketIdentifier;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@ -19,6 +20,6 @@ public class SetCompressionPacket implements ServerPacket {
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return 0x03;
|
||||
return ServerPacketIdentifier.LOGIN_SET_COMPRESSION;
|
||||
}
|
||||
}
|
||||
|
@ -102,9 +102,9 @@ public class ChunkDataPacket implements ServerPacket {
|
||||
final BlockPosition blockPosition = ChunkUtils.getBlockPosition(index, chunkX, chunkZ);
|
||||
|
||||
NBTCompound nbt = new NBTCompound()
|
||||
.setDouble("x", blockPosition.getX())
|
||||
.setDouble("y", blockPosition.getY())
|
||||
.setDouble("z", blockPosition.getZ());
|
||||
.setInt("x", blockPosition.getX())
|
||||
.setInt("y", blockPosition.getY())
|
||||
.setInt("z", blockPosition.getZ());
|
||||
|
||||
final short customBlockId = customBlocksId[index];
|
||||
final CustomBlock customBlock = BLOCK_MANAGER.getCustomBlock(customBlockId);
|
||||
|
@ -5,7 +5,7 @@ import net.minestom.server.network.packet.server.ServerPacketIdentifier;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class EntityPacket implements ServerPacket {
|
||||
public class EntityMovementPacket implements ServerPacket {
|
||||
|
||||
public int entityId;
|
||||
|
@ -1,4 +1,4 @@
|
||||
package net.minestom.server.network.packet.server.login;
|
||||
package net.minestom.server.network.packet.server.play;
|
||||
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.entity.GameMode;
|
@ -29,6 +29,7 @@ public class PluginMessagePacket implements ServerPacket {
|
||||
*
|
||||
* @return the current brand name packet
|
||||
*/
|
||||
@NotNull
|
||||
public static PluginMessagePacket getBrandPacket() {
|
||||
PluginMessagePacket brandMessage = new PluginMessagePacket();
|
||||
brandMessage.channel = "minecraft:brand";
|
||||
|
@ -4,9 +4,11 @@ import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import net.minestom.server.extras.mojangAuth.Decrypter;
|
||||
import net.minestom.server.extras.mojangAuth.Encrypter;
|
||||
import net.minestom.server.extras.mojangAuth.MojangCrypt;
|
||||
import net.minestom.server.network.ConnectionState;
|
||||
import net.minestom.server.network.netty.codec.PacketCompressor;
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import net.minestom.server.network.packet.server.login.SetCompressionPacket;
|
||||
@ -16,6 +18,8 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Represents a networking connection with Netty.
|
||||
@ -25,17 +29,30 @@ import java.net.SocketAddress;
|
||||
public class NettyPlayerConnection extends PlayerConnection {
|
||||
|
||||
private final SocketChannel channel;
|
||||
|
||||
private SocketAddress remoteAddress;
|
||||
@Getter
|
||||
private boolean encrypted = false;
|
||||
@Getter
|
||||
private boolean compressed = false;
|
||||
|
||||
//Could be null. Only used for Mojang Auth
|
||||
@Getter
|
||||
@Setter
|
||||
private byte[] nonce = new byte[4];
|
||||
|
||||
private String loginUsername;
|
||||
private String serverAddress;
|
||||
private int serverPort;
|
||||
|
||||
// Used for the login plugin request packet, to retrieve the channel from a message id,
|
||||
// cleared once the player enters the play state
|
||||
private final Map<Integer, String> pluginRequestMap = new ConcurrentHashMap<>();
|
||||
|
||||
public NettyPlayerConnection(@NotNull SocketChannel channel) {
|
||||
super();
|
||||
this.channel = channel;
|
||||
this.remoteAddress = channel.remoteAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -66,25 +83,25 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
|
||||
@Override
|
||||
public void sendPacket(@NotNull ByteBuf buffer, boolean copy) {
|
||||
if ((encrypted || compressed) && copy) {
|
||||
if (copy) {
|
||||
buffer = buffer.copy();
|
||||
buffer.retain();
|
||||
channel.writeAndFlush(buffer);
|
||||
buffer.release();
|
||||
} else {
|
||||
getChannel().writeAndFlush(buffer);
|
||||
channel.writeAndFlush(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writePacket(@NotNull ByteBuf buffer, boolean copy) {
|
||||
if ((encrypted || compressed) && copy) {
|
||||
if (copy) {
|
||||
buffer = buffer.copy();
|
||||
buffer.retain();
|
||||
channel.write(buffer);
|
||||
buffer.release();
|
||||
} else {
|
||||
getChannel().write(buffer);
|
||||
channel.write(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,7 +118,18 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
@NotNull
|
||||
@Override
|
||||
public SocketAddress getRemoteAddress() {
|
||||
return getChannel().remoteAddress();
|
||||
return remoteAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the internal remote address field.
|
||||
* <p>
|
||||
* Mostly unsafe, used internally when interacting with a proxy.
|
||||
*
|
||||
* @param remoteAddress the new connection remote address
|
||||
*/
|
||||
public void setRemoteAddress(@NotNull SocketAddress remoteAddress) {
|
||||
this.remoteAddress = remoteAddress;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -115,7 +143,28 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the server address that the client used to connect.
|
||||
* Retrieves the username received from the client during connection.
|
||||
* <p>
|
||||
* This value has not been checked and could be anything.
|
||||
*
|
||||
* @return the username given by the client, unchecked
|
||||
*/
|
||||
@Nullable
|
||||
public String getLoginUsername() {
|
||||
return loginUsername;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the internal login username field.
|
||||
*
|
||||
* @param loginUsername the new login username field
|
||||
*/
|
||||
public void UNSAFE_setLoginUsername(@NotNull String loginUsername) {
|
||||
this.loginUsername = loginUsername;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the server address that the client used to connect.
|
||||
* <p>
|
||||
* WARNING: it is given by the client, it is possible for it to be wrong.
|
||||
*
|
||||
@ -127,7 +176,7 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the server port that the client used to connect.
|
||||
* Gets the server port that the client used to connect.
|
||||
* <p>
|
||||
* WARNING: it is given by the client, it is possible for it to be wrong.
|
||||
*
|
||||
@ -137,6 +186,45 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
return serverPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an entry to the plugin request map.
|
||||
* <p>
|
||||
* Only working if {@link #getConnectionState()} is {@link net.minestom.server.network.ConnectionState#LOGIN}.
|
||||
*
|
||||
* @param messageId the message id
|
||||
* @param channel the packet channel
|
||||
* @throws IllegalStateException if a messageId with the value {@code messageId} already exists for this connection
|
||||
*/
|
||||
public void addPluginRequestEntry(int messageId, @NotNull String channel) {
|
||||
if (!getConnectionState().equals(ConnectionState.LOGIN)) {
|
||||
return;
|
||||
}
|
||||
Check.stateCondition(pluginRequestMap.containsKey(messageId), "You cannot have two messageId with the same value");
|
||||
this.pluginRequestMap.put(messageId, channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a request channel from a message id, previously cached using {@link #addPluginRequestEntry(int, String)}.
|
||||
* <p>
|
||||
* Be aware that the internal map is cleared once the player enters the play state.
|
||||
*
|
||||
* @param messageId the message id
|
||||
* @return the channel linked to the message id, null if not found
|
||||
*/
|
||||
@Nullable
|
||||
public String getPluginRequestChannel(int messageId) {
|
||||
return pluginRequestMap.get(messageId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConnectionState(@NotNull ConnectionState connectionState) {
|
||||
super.setConnectionState(connectionState);
|
||||
// Clear the plugin request map (since it is not used anymore)
|
||||
if (connectionState.equals(ConnectionState.PLAY)) {
|
||||
this.pluginRequestMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used in {@link net.minestom.server.network.packet.client.handshake.HandshakePacket} to change the internal fields.
|
||||
*
|
||||
|
@ -2,14 +2,13 @@ package net.minestom.server.network.player;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.chat.ChatColor;
|
||||
import net.minestom.server.chat.ColoredText;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.network.ConnectionState;
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import net.minestom.server.network.packet.server.login.LoginDisconnect;
|
||||
import net.minestom.server.network.packet.server.login.LoginDisconnectPacket;
|
||||
import net.minestom.server.network.packet.server.play.DisconnectPacket;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@ -23,14 +22,6 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
public abstract class PlayerConnection {
|
||||
|
||||
private Player player;
|
||||
//Could be null. Only used for Mojang Auth
|
||||
@Getter
|
||||
@Setter
|
||||
private String loginUsername;
|
||||
//Could be null. Only used for Mojang Auth
|
||||
@Getter
|
||||
@Setter
|
||||
private byte[] nonce = new byte[4];
|
||||
private ConnectionState connectionState;
|
||||
private boolean online;
|
||||
|
||||
@ -64,7 +55,7 @@ public abstract class PlayerConnection {
|
||||
if (count > MinecraftServer.getRateLimit()) {
|
||||
// Sent too many packets
|
||||
if (connectionState == ConnectionState.LOGIN) {
|
||||
sendPacket(new LoginDisconnect("Too Many Packets"));
|
||||
sendPacket(new LoginDisconnectPacket("Too Many Packets"));
|
||||
} else {
|
||||
DisconnectPacket disconnectPacket = new DisconnectPacket();
|
||||
disconnectPacket.message = rateLimitKickMessage;
|
||||
|
@ -1,14 +1,15 @@
|
||||
package net.minestom.server.permission;
|
||||
|
||||
import net.minestom.server.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Basic {@link Permission} implementation that only requires the permission to be given to the {@link CommandSender} to be considered applied
|
||||
* (eg. no arguments)
|
||||
*/
|
||||
public class BasicPermission implements Permission {
|
||||
public class BasicPermission implements Permission<Object> {
|
||||
@Override
|
||||
public boolean isValidFor(CommandSender commandSender) {
|
||||
public boolean isValidFor(@NotNull CommandSender commandSender, Object data) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -6,10 +6,14 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Representation of a permission granted to a {@link CommandSender}
|
||||
* Representation of a permission granted to a {@link CommandSender}.
|
||||
*
|
||||
* @param <T> the type of data that this permission can handle in {@link #isValidFor(CommandSender, Object)}.
|
||||
* Used if you want to allow passing additional data to check if the permission is valid in a certain situation,
|
||||
* you can default it to {@link Object} if you do not need it.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Permission {
|
||||
public interface Permission<T> {
|
||||
|
||||
/**
|
||||
* Does the given {@link CommandSender} have the permission represented by this object?
|
||||
@ -18,22 +22,23 @@ public interface Permission {
|
||||
* have this permission and validate the condition in this method.
|
||||
*
|
||||
* @param commandSender the command sender
|
||||
* @param data the optional data (eg the number of home possible, placing a block at X position)
|
||||
* @return true if the commandSender possesses this permission
|
||||
*/
|
||||
boolean isValidFor(CommandSender commandSender);
|
||||
boolean isValidFor(@NotNull CommandSender commandSender, @Nullable T data);
|
||||
|
||||
/**
|
||||
* Writes any required data for this permission inside the given destination
|
||||
* Writes any required data for this permission inside the given destination.
|
||||
*
|
||||
* @param destination {@link Data} to write to
|
||||
* @param destination the {@link Data} to write to
|
||||
*/
|
||||
default void write(@NotNull Data destination) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads any required data for this permission from the given destination
|
||||
* Reads any required data for this permission from the given destination.
|
||||
*
|
||||
* @param source {@link Data} to read from
|
||||
* @param source the {@link Data} to read from
|
||||
* @return this for chaining
|
||||
*/
|
||||
default Permission read(@Nullable Data source) {
|
||||
|
@ -86,9 +86,12 @@ public class Sidebar implements Scoreboard {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ScoreboardLine}
|
||||
* Creates a new {@link ScoreboardLine}.
|
||||
*
|
||||
* @param scoreboardLine The new scoreboard line
|
||||
* @param scoreboardLine the new scoreboard line
|
||||
* @throws IllegalStateException if the sidebar cannot take more line
|
||||
* @throws IllegalArgumentException if the sidebar already contains the line {@code scoreboardLine}
|
||||
* or has a line with the same id
|
||||
*/
|
||||
public void createLine(ScoreboardLine scoreboardLine) {
|
||||
synchronized (lines) {
|
||||
@ -114,7 +117,7 @@ public class Sidebar implements Scoreboard {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a {@link ScoreboardLine} content through the given identifier
|
||||
* Updates a {@link ScoreboardLine} content through the given identifier.
|
||||
*
|
||||
* @param id The identifier of the {@link ScoreboardLine}
|
||||
* @param content The new content for the {@link ScoreboardLine}
|
||||
|
@ -6,6 +6,7 @@ import net.minestom.server.instance.Chunk;
|
||||
import net.minestom.server.instance.Instance;
|
||||
import net.minestom.server.instance.InstanceContainer;
|
||||
import net.minestom.server.instance.SharedInstance;
|
||||
import net.minestom.server.utils.callback.validator.EntityValidator;
|
||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||
import net.minestom.server.utils.thread.MinestomThread;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -16,7 +17,6 @@ import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Used to link chunks into multiple groups.
|
||||
@ -216,12 +216,12 @@ public abstract class ThreadProvider {
|
||||
* @param condition the condition which confirm if the update happens or not
|
||||
*/
|
||||
protected void conditionalEntityUpdate(@NotNull Instance instance, @NotNull Chunk chunk, long time,
|
||||
@Nullable Function<Entity, Boolean> condition) {
|
||||
@Nullable EntityValidator condition) {
|
||||
final Set<Entity> entities = instance.getChunkEntities(chunk);
|
||||
|
||||
if (!entities.isEmpty()) {
|
||||
for (Entity entity : entities) {
|
||||
if (condition != null && !condition.apply(entity))
|
||||
if (condition != null && !condition.isValid(entity))
|
||||
continue;
|
||||
entity.tick(time);
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectCollection;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.utils.thread.MinestomThread;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
@ -18,10 +19,10 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
* <p>
|
||||
* {@link Task} first need to be built with {@link #buildTask(Runnable)}, you can then specify a delay with as example
|
||||
* {@link TaskBuilder#delay(long, net.minestom.server.utils.time.TimeUnit)}
|
||||
* or {@link TaskBuilder#repeat(long, net.minestom.server.utils.time.TimeUnit)}
|
||||
* and to finally schedule {@link TaskBuilder#schedule()}.
|
||||
* or {@link TaskBuilder#repeat(long, net.minestom.server.utils.time.TimeUnit)},
|
||||
* and to finally schedule: {@link TaskBuilder#schedule()}.
|
||||
* <p>
|
||||
* Shutdown {@link Task} are built with {@link #buildShutdownTask(Runnable)}.
|
||||
* Shutdown tasks are built with {@link #buildShutdownTask(Runnable)} and are executed, as the name implies, when the server stops.
|
||||
*/
|
||||
public final class SchedulerManager {
|
||||
|
||||
@ -64,7 +65,8 @@ public final class SchedulerManager {
|
||||
* @param runnable The {@link Task} to run when scheduled
|
||||
* @return the {@link TaskBuilder}
|
||||
*/
|
||||
public TaskBuilder buildTask(Runnable runnable) {
|
||||
@NotNull
|
||||
public TaskBuilder buildTask(@NotNull Runnable runnable) {
|
||||
return new TaskBuilder(this, runnable);
|
||||
}
|
||||
|
||||
@ -74,28 +76,20 @@ public final class SchedulerManager {
|
||||
* @param runnable The shutdown {@link Task} to run when scheduled
|
||||
* @return the {@link TaskBuilder}
|
||||
*/
|
||||
public TaskBuilder buildShutdownTask(Runnable runnable) {
|
||||
@NotNull
|
||||
public TaskBuilder buildShutdownTask(@NotNull Runnable runnable) {
|
||||
return new TaskBuilder(this, runnable, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes/Forces the end of a {@link Task}.
|
||||
* <p>
|
||||
* {@link Task#cancel()} can also be used instead.
|
||||
*
|
||||
* @param task The {@link Task} to remove
|
||||
*/
|
||||
public void removeTask(Task task) {
|
||||
synchronized (tasks) {
|
||||
this.tasks.remove(task.getId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes/Forces the end of a {@link Task}.
|
||||
*
|
||||
* @param task The {@link Task} to remove
|
||||
*/
|
||||
public void removeShutdownTask(Task task) {
|
||||
this.shutdownTasks.remove(task.getId());
|
||||
public void removeTask(@NotNull Task task) {
|
||||
task.cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -141,6 +135,7 @@ public final class SchedulerManager {
|
||||
*
|
||||
* @return a {@link Collection} with all the registered {@link Task}
|
||||
*/
|
||||
@NotNull
|
||||
public ObjectCollection<Task> getTasks() {
|
||||
return tasks.values();
|
||||
}
|
||||
@ -150,6 +145,7 @@ public final class SchedulerManager {
|
||||
*
|
||||
* @return a {@link Collection} with all the registered shutdown {@link Task}
|
||||
*/
|
||||
@NotNull
|
||||
public ObjectCollection<Task> getShutdownTasks() {
|
||||
return shutdownTasks.values();
|
||||
}
|
||||
@ -159,6 +155,7 @@ public final class SchedulerManager {
|
||||
*
|
||||
* @return the execution service for all the registered {@link Task}
|
||||
*/
|
||||
@NotNull
|
||||
public ExecutorService getBatchesPool() {
|
||||
return batchesPool;
|
||||
}
|
||||
@ -168,6 +165,7 @@ public final class SchedulerManager {
|
||||
*
|
||||
* @return the scheduled execution service for all the registered {@link Task}
|
||||
*/
|
||||
@NotNull
|
||||
public ScheduledExecutorService getTimerExecutionService() {
|
||||
return timerExecutionService;
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
package net.minestom.server.timer;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -37,7 +40,7 @@ public class Task implements Runnable {
|
||||
* @param delay The time to delay
|
||||
* @param repeat The time until the repetition
|
||||
*/
|
||||
public Task(SchedulerManager schedulerManager, Runnable runnable, boolean shutdown, long delay, long repeat) {
|
||||
public Task(@NotNull SchedulerManager schedulerManager, @NotNull Runnable runnable, boolean shutdown, long delay, long repeat) {
|
||||
this.schedulerManager = schedulerManager;
|
||||
this.runnable = runnable;
|
||||
this.shutdown = shutdown;
|
||||
@ -84,6 +87,7 @@ public class Task implements Runnable {
|
||||
*
|
||||
* @return the current stats of the task
|
||||
*/
|
||||
@NotNull
|
||||
public TaskStatus getStatus() {
|
||||
if (this.future == null) return TaskStatus.SCHEDULED;
|
||||
if (this.future.isCancelled()) return TaskStatus.CANCELLED;
|
||||
@ -116,13 +120,16 @@ public class Task implements Runnable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the task.
|
||||
* Removes the task from the {@link SchedulerManager} map.
|
||||
*/
|
||||
private void finish() {
|
||||
if (this.shutdown)
|
||||
this.schedulerManager.removeShutdownTask(this);
|
||||
else
|
||||
this.schedulerManager.removeTask(this);
|
||||
Int2ObjectMap<Task> taskMap = shutdown ?
|
||||
this.schedulerManager.shutdownTasks :
|
||||
this.schedulerManager.tasks;
|
||||
|
||||
synchronized (taskMap) {
|
||||
taskMap.remove(getId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -2,6 +2,7 @@ package net.minestom.server.timer;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import net.minestom.server.utils.time.TimeUnit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A builder which represents a fluent Object to schedule tasks.
|
||||
@ -30,7 +31,7 @@ public class TaskBuilder {
|
||||
* @param schedulerManager The manager for the tasks
|
||||
* @param runnable The task to run when scheduled
|
||||
*/
|
||||
public TaskBuilder(SchedulerManager schedulerManager, Runnable runnable) {
|
||||
public TaskBuilder(@NotNull SchedulerManager schedulerManager, @NotNull Runnable runnable) {
|
||||
this(schedulerManager, runnable, false);
|
||||
}
|
||||
|
||||
@ -41,7 +42,7 @@ public class TaskBuilder {
|
||||
* @param runnable The task to run when scheduled
|
||||
* @param shutdown Defines whether the task is a shutdown task
|
||||
*/
|
||||
public TaskBuilder(SchedulerManager schedulerManager, Runnable runnable, boolean shutdown) {
|
||||
public TaskBuilder(@NotNull SchedulerManager schedulerManager, @NotNull Runnable runnable, boolean shutdown) {
|
||||
this.schedulerManager = schedulerManager;
|
||||
this.runnable = runnable;
|
||||
this.shutdown = shutdown;
|
||||
@ -54,7 +55,8 @@ public class TaskBuilder {
|
||||
* @param unit The unit of time for {@code time}
|
||||
* @return this builder, for chaining
|
||||
*/
|
||||
public TaskBuilder delay(long time, TimeUnit unit) {
|
||||
@NotNull
|
||||
public TaskBuilder delay(long time, @NotNull TimeUnit unit) {
|
||||
this.delay = unit.toMilliseconds(time);
|
||||
return this;
|
||||
}
|
||||
@ -66,7 +68,8 @@ public class TaskBuilder {
|
||||
* @param unit The {@link TimeUnit} for {@code time}
|
||||
* @return this builder, for chaining
|
||||
*/
|
||||
public TaskBuilder repeat(long time, TimeUnit unit) {
|
||||
@NotNull
|
||||
public TaskBuilder repeat(long time, @NotNull TimeUnit unit) {
|
||||
this.repeat = unit.toMilliseconds(time);
|
||||
return this;
|
||||
}
|
||||
@ -76,6 +79,7 @@ public class TaskBuilder {
|
||||
*
|
||||
* @return this builder, for chaining
|
||||
*/
|
||||
@NotNull
|
||||
public TaskBuilder clearDelay() {
|
||||
this.delay = 0L;
|
||||
return this;
|
||||
@ -86,16 +90,18 @@ public class TaskBuilder {
|
||||
*
|
||||
* @return this builder, for chaining
|
||||
*/
|
||||
@NotNull
|
||||
public TaskBuilder clearRepeat() {
|
||||
this.repeat = 0L;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule this {@link Task} for execution.
|
||||
* Schedules this {@link Task} for execution.
|
||||
*
|
||||
* @return the built {@link Task}
|
||||
*/
|
||||
@NotNull
|
||||
public Task schedule() {
|
||||
Task task = new Task(
|
||||
this.schedulerManager,
|
||||
|
@ -174,17 +174,41 @@ public class BlockPosition {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the distance to another block position.
|
||||
* Gets the manhattan distance to another block position.
|
||||
*
|
||||
* @param blockPosition the block position to check the distance
|
||||
* @return the distance between 'this' and {@code blockPosition}
|
||||
*/
|
||||
public int getDistance(@NotNull BlockPosition blockPosition) {
|
||||
public int getManhattanDistance(@NotNull BlockPosition blockPosition) {
|
||||
return Math.abs(getX() - blockPosition.getX()) +
|
||||
Math.abs(getY() - blockPosition.getY()) +
|
||||
Math.abs(getZ() - blockPosition.getZ());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the distance to another block position.
|
||||
* In cases where performance matters, {@link #getDistanceSquared(BlockPosition)} should be used
|
||||
* as it does not perform the expensive Math.sqrt method.
|
||||
*
|
||||
* @param blockPosition the block position to check the distance
|
||||
* @return the distance between 'this' and {@code blockPosition}
|
||||
*/
|
||||
public double getDistance(@NotNull BlockPosition blockPosition) {
|
||||
return Math.sqrt(getDistanceSquared(blockPosition));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the square distance to another block position.
|
||||
*
|
||||
* @param blockPosition the block position to check the distance
|
||||
* @return the distance between 'this' and {@code blockPosition}
|
||||
*/
|
||||
public int getDistanceSquared(@NotNull BlockPosition blockPosition) {
|
||||
return MathUtils.square(getX() - blockPosition.getX()) +
|
||||
MathUtils.square(getY() - blockPosition.getY()) +
|
||||
MathUtils.square(getZ() - blockPosition.getZ());
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies this block position.
|
||||
*
|
||||
|
@ -1,9 +1,13 @@
|
||||
package net.minestom.server.utils;
|
||||
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.attribute.Attribute;
|
||||
import net.minestom.server.attribute.AttributeOperation;
|
||||
import net.minestom.server.chat.ChatParser;
|
||||
import net.minestom.server.chat.ColoredText;
|
||||
import net.minestom.server.data.Data;
|
||||
import net.minestom.server.data.DataType;
|
||||
import net.minestom.server.data.NbtDataImpl;
|
||||
import net.minestom.server.inventory.Inventory;
|
||||
import net.minestom.server.item.Enchantment;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
@ -15,6 +19,9 @@ import net.minestom.server.item.metadata.ItemMeta;
|
||||
import net.minestom.server.registry.Registries;
|
||||
import net.minestom.server.utils.binary.BinaryReader;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jglrxavpok.hephaistos.nbt.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -40,7 +47,7 @@ public final class NBTUtils {
|
||||
* @param items the items to save
|
||||
* @param destination the inventory destination
|
||||
*/
|
||||
public static void loadAllItems(NBTList<NBTCompound> items, Inventory destination) {
|
||||
public static void loadAllItems(@NotNull NBTList<NBTCompound> items, @NotNull Inventory destination) {
|
||||
destination.clear();
|
||||
for (NBTCompound tag : items) {
|
||||
Material item = Registries.getMaterial(tag.getString("id"));
|
||||
@ -55,7 +62,7 @@ public final class NBTUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void saveAllItems(NBTList<NBTCompound> list, Inventory inventory) {
|
||||
public static void saveAllItems(@NotNull NBTList<NBTCompound> list, @NotNull Inventory inventory) {
|
||||
for (int i = 0; i < inventory.getSize(); i++) {
|
||||
final ItemStack stack = inventory.getItemStack(i);
|
||||
NBTCompound nbt = new NBTCompound();
|
||||
@ -72,7 +79,8 @@ public final class NBTUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void writeEnchant(NBTCompound nbt, String listName, Map<Enchantment, Short> enchantmentMap) {
|
||||
public static void writeEnchant(@NotNull NBTCompound nbt, @NotNull String listName,
|
||||
@NotNull Map<Enchantment, Short> enchantmentMap) {
|
||||
NBTList<NBTCompound> enchantList = new NBTList<>(NBTTypes.TAG_Compound);
|
||||
for (Map.Entry<Enchantment, Short> entry : enchantmentMap.entrySet()) {
|
||||
final Enchantment enchantment = entry.getKey();
|
||||
@ -86,7 +94,8 @@ public final class NBTUtils {
|
||||
nbt.set(listName, enchantList);
|
||||
}
|
||||
|
||||
public static ItemStack readItemStack(BinaryReader reader) {
|
||||
@NotNull
|
||||
public static ItemStack readItemStack(@NotNull BinaryReader reader) {
|
||||
final boolean present = reader.readBoolean();
|
||||
|
||||
if (!present) {
|
||||
@ -116,7 +125,7 @@ public final class NBTUtils {
|
||||
return item;
|
||||
}
|
||||
|
||||
public static void loadDataIntoItem(ItemStack item, NBTCompound nbt) {
|
||||
public static void loadDataIntoItem(@NotNull ItemStack item, @NotNull NBTCompound nbt) {
|
||||
if (nbt.containsKey("Damage")) item.setDamage(nbt.getInt("Damage"));
|
||||
if (nbt.containsKey("Unbreakable")) item.setUnbreakable(nbt.getInt("Unbreakable") == 1);
|
||||
if (nbt.containsKey("HideFlags")) item.setHideFlag(nbt.getInt("HideFlags"));
|
||||
@ -179,9 +188,24 @@ public final class NBTUtils {
|
||||
|
||||
// Meta specific field
|
||||
final ItemMeta itemMeta = item.getItemMeta();
|
||||
if (itemMeta == null)
|
||||
return;
|
||||
itemMeta.read(nbt);
|
||||
if (itemMeta != null) {
|
||||
itemMeta.read(nbt);
|
||||
}
|
||||
|
||||
NbtDataImpl customData = null;
|
||||
for (String key : nbt.getKeys()) {
|
||||
if (key.startsWith(NbtDataImpl.KEY_PREFIX)) {
|
||||
if (customData == null) {
|
||||
customData = new NbtDataImpl();
|
||||
item.setData(customData);
|
||||
}
|
||||
final NBT keyNbt = nbt.get(key);
|
||||
|
||||
final String dataKey = key.replaceFirst(NbtDataImpl.KEY_PREFIX, "");
|
||||
final Object dataValue = fromNBT(keyNbt);
|
||||
customData.set(dataKey, dataValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void loadEnchantments(NBTList<NBTCompound> enchantments, EnchantmentSetter setter) {
|
||||
@ -226,7 +250,7 @@ public final class NBTUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void saveDataIntoNBT(ItemStack itemStack, NBTCompound itemNBT) {
|
||||
public static void saveDataIntoNBT(@NotNull ItemStack itemStack, @NotNull NBTCompound itemNBT) {
|
||||
// Unbreakable
|
||||
if (itemStack.isUnbreakable()) {
|
||||
itemNBT.setInt("Unbreakable", 1);
|
||||
@ -317,11 +341,106 @@ public final class NBTUtils {
|
||||
// End custom model data
|
||||
|
||||
// Start custom meta
|
||||
final ItemMeta itemMeta = itemStack.getItemMeta();
|
||||
if (itemMeta != null) {
|
||||
itemMeta.write(itemNBT);
|
||||
{
|
||||
final ItemMeta itemMeta = itemStack.getItemMeta();
|
||||
if (itemMeta != null) {
|
||||
itemMeta.write(itemNBT);
|
||||
}
|
||||
}
|
||||
// End custom meta
|
||||
|
||||
// Start NbtData data
|
||||
{
|
||||
final Data data = itemStack.getData();
|
||||
if (data instanceof NbtDataImpl) {
|
||||
NbtDataImpl nbtData = (NbtDataImpl) data;
|
||||
nbtData.writeToNbt(itemNBT);
|
||||
}
|
||||
}
|
||||
// End NbtData
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an object into its {@link NBT} equivalent.
|
||||
* <p>
|
||||
* If {@code type} is not a primitive type or primitive array and {@code supportDataType} is true,
|
||||
* the data will be encoded with the appropriate {@link DataType} into a byte array.
|
||||
*
|
||||
* @param value the value to convert
|
||||
* @param type the type of the value, used to know which {@link DataType} to use if {@code value} is not a primitive type
|
||||
* @param supportDataType true to allow using a {@link DataType} to encode {@code value} into a byte array if not a primitive type
|
||||
* @return the converted value, null if {@code type} is not a primitive type and {@code supportDataType} is false
|
||||
*/
|
||||
@Nullable
|
||||
public static NBT toNBT(@NotNull Object value, @NotNull Class type, boolean supportDataType) {
|
||||
type = PrimitiveConversion.getObjectClass(type);
|
||||
if (type.equals(Boolean.class)) {
|
||||
// No boolean type in NBT
|
||||
return new NBTByte((byte) (((boolean) value) ? 1 : 0));
|
||||
} else if (type.equals(Byte.class)) {
|
||||
return new NBTByte((byte) value);
|
||||
} else if (type.equals(Character.class)) {
|
||||
// No char type in NBT
|
||||
return new NBTShort((short) value);
|
||||
} else if (type.equals(Short.class)) {
|
||||
return new NBTShort((short) value);
|
||||
} else if (type.equals(Integer.class)) {
|
||||
return new NBTInt((int) value);
|
||||
} else if (type.equals(Long.class)) {
|
||||
return new NBTLong((long) value);
|
||||
} else if (type.equals(Float.class)) {
|
||||
return new NBTFloat((float) value);
|
||||
} else if (type.equals(Double.class)) {
|
||||
return new NBTDouble((double) value);
|
||||
} else if (type.equals(String.class)) {
|
||||
return new NBTString((String) value);
|
||||
} else if (type.equals(Byte[].class)) {
|
||||
return new NBTByteArray((byte[]) value);
|
||||
} else if (type.equals(Integer[].class)) {
|
||||
return new NBTIntArray((int[]) value);
|
||||
} else if (type.equals(Long[].class)) {
|
||||
return new NBTLongArray((long[]) value);
|
||||
} else {
|
||||
if (supportDataType) {
|
||||
// Custom NBT type, try to encode using the data manager
|
||||
DataType dataType = MinecraftServer.getDataManager().getDataType(type);
|
||||
Check.notNull(dataType, "The type '" + type + "' is not registered in DataManager and not a primitive type.");
|
||||
|
||||
BinaryWriter writer = new BinaryWriter();
|
||||
dataType.encode(writer, value);
|
||||
|
||||
final byte[] encodedValue = writer.toByteArray();
|
||||
|
||||
return new NBTByteArray(encodedValue);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a nbt object to its raw value.
|
||||
* <p>
|
||||
* Currently support number, string, byte/int/long array.
|
||||
*
|
||||
* @param nbt the nbt tag to convert
|
||||
* @return the value representation of a tag
|
||||
* @throws UnsupportedOperationException if the tag type is not supported
|
||||
*/
|
||||
public static Object fromNBT(@NotNull NBT nbt) {
|
||||
if (nbt instanceof NBTNumber) {
|
||||
return ((NBTNumber) nbt).getValue();
|
||||
} else if (nbt instanceof NBTString) {
|
||||
return ((NBTString) nbt).getValue();
|
||||
} else if (nbt instanceof NBTByteArray) {
|
||||
return ((NBTByteArray) nbt).getValue();
|
||||
} else if (nbt instanceof NBTIntArray) {
|
||||
return ((NBTIntArray) nbt).getValue();
|
||||
} else if (nbt instanceof NBTLongArray) {
|
||||
return ((NBTLongArray) nbt).getValue();
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException("NBT type " + nbt.getClass() + " is not handled properly.");
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
|
@ -22,10 +22,11 @@ public class NamespaceID implements CharSequence {
|
||||
* Extracts the domain from the namespace ID. "minecraft:stone" would return "minecraft".
|
||||
* If no ':' character is found, "minecraft" is returned.
|
||||
*
|
||||
* @param namespaceID
|
||||
* @param namespaceID the namespace id to get the domain from
|
||||
* @return the domain of the namespace ID
|
||||
*/
|
||||
public static String getDomain(String namespaceID) {
|
||||
@NotNull
|
||||
public static String getDomain(@NotNull String namespaceID) {
|
||||
final int index = namespaceID.indexOf(':');
|
||||
if (index < 0)
|
||||
return "minecraft";
|
||||
@ -37,10 +38,10 @@ public class NamespaceID implements CharSequence {
|
||||
* Extracts the path from the namespace ID. "minecraft:blocks/stone" would return "blocks/stone".
|
||||
* If no ':' character is found, the <pre>namespaceID</pre> is returned.
|
||||
*
|
||||
* @param namespaceID
|
||||
* @param namespaceID the namespace id to get the path from
|
||||
* @return the path of the namespace ID
|
||||
*/
|
||||
public static String getPath(String namespaceID) {
|
||||
public static String getPath(@NotNull String namespaceID) {
|
||||
final int index = namespaceID.indexOf(':');
|
||||
if (index < 0)
|
||||
return namespaceID;
|
||||
@ -61,7 +62,7 @@ public class NamespaceID implements CharSequence {
|
||||
return from(getDomain(id), getPath(id));
|
||||
}
|
||||
|
||||
private NamespaceID(String path) {
|
||||
private NamespaceID(@NotNull String path) {
|
||||
final int index = path.indexOf(':');
|
||||
if (index < 0) {
|
||||
this.domain = "minecraft";
|
||||
|
@ -4,6 +4,7 @@ import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Class used to write packets.
|
||||
@ -20,7 +21,7 @@ public final class PacketUtils {
|
||||
* @param buf the recipient of {@code packet}
|
||||
* @param packet the packet to write into {@code buf}
|
||||
*/
|
||||
public static void writePacket(ByteBuf buf, ServerPacket packet) {
|
||||
public static void writePacket(@NotNull ByteBuf buf, @NotNull ServerPacket packet) {
|
||||
|
||||
final ByteBuf packetBuffer = getPacketBuffer(packet);
|
||||
|
||||
@ -33,7 +34,8 @@ public final class PacketUtils {
|
||||
* @param packet the packet to write
|
||||
* @return a {@link ByteBuf} containing {@code packet}
|
||||
*/
|
||||
public static ByteBuf writePacket(ServerPacket packet) {
|
||||
@NotNull
|
||||
public static ByteBuf writePacket(@NotNull ServerPacket packet) {
|
||||
final ByteBuf packetBuffer = getPacketBuffer(packet);
|
||||
|
||||
// Add 5 for the packet id and for the packet size
|
||||
@ -52,7 +54,7 @@ public final class PacketUtils {
|
||||
* @param packetBuffer the buffer containing the raw packet data
|
||||
* @param packetId the packet id
|
||||
*/
|
||||
private static void writePacket(ByteBuf buf, ByteBuf packetBuffer, int packetId) {
|
||||
private static void writePacket(@NotNull ByteBuf buf, @NotNull ByteBuf packetBuffer, int packetId) {
|
||||
Utils.writeVarIntBuf(buf, packetId);
|
||||
buf.writeBytes(packetBuffer);
|
||||
}
|
||||
@ -63,7 +65,8 @@ public final class PacketUtils {
|
||||
* @param packet the packet to write
|
||||
* @return the {@link ByteBuf} containing the raw packet data
|
||||
*/
|
||||
private static ByteBuf getPacketBuffer(ServerPacket packet) {
|
||||
@NotNull
|
||||
private static ByteBuf getPacketBuffer(@NotNull ServerPacket packet) {
|
||||
BinaryWriter writer = new BinaryWriter();
|
||||
packet.write(writer);
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user