Merge branch 'master' into jline

This commit is contained in:
TheMode 2021-04-13 02:18:10 +02:00
commit 56439a2330
440 changed files with 14122 additions and 5872 deletions

1248
.editorconfig Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
#### **Did you find a bug?**
* Open a new GitHub issue if it's not already reported.
* Use the relevant bug report template to create the issue.
* Explain it clearly, with steps (or code) to reproduce it.
#### **Did you write some code that fixes a bug?**
* Open a new GitHub pull-request with the commits if it hasn't already been proposed.

View File

@ -1,22 +0,0 @@
---
name: Bug report
labels: Bug
about: Use this to report unexpected behavior (bugs and errors).
---
### What is the current behavior?
### What is the expected behavior?
### What steps will reproduce the problem?
### What operating system and java version is being used?
### If there is an exception, use pastebin/hastebin (NB: no expiry date!) to send the stacktrace
### Additional information / Possible thoughts on why this bug is happening

View File

@ -1,15 +0,0 @@
---
name: Feature Request
labels: Enhancement
about: Use this to request an addition (feature).
---
### What would you like added/changed?
### Why do you think this is a good addition/alteration?
### Why do you want this to be added?
### Additional Information

View File

@ -1,7 +0,0 @@
---
name: Question
labels: Question
about: Use this to ask a question.
---
### Your Question:

24
.github/README.md vendored
View File

@ -1,16 +1,18 @@
# Minestom
![banner](banner.png)
[![license](https://img.shields.io/github/license/Minestom/Minestom.svg)](../LICENSE)
[![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme)
[![discord-banner](https://discordapp.com/api/guilds/706185253441634317/widget.png?style=banner2)](https://discord.gg/pkFRvqB)
[![license](https://img.shields.io/github/license/Minestom/Minestom?style=for-the-badge&color=b2204c)](../LICENSE)
[![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=for-the-badge)](https://github.com/RichardLitt/standard-readme)
[![javadocs](https://img.shields.io/badge/documentation-javadocs-4d7a97?style=for-the-badge)](https://minestom.github.io/Minestom/)
[![wiki](https://img.shields.io/badge/documentation-wiki-74aad6?style=for-the-badge)](https://wiki.minestom.com/)
[![discord-banner](https://img.shields.io/discord/706185253441634317?label=discord&style=for-the-badge&color=7289da)](https://discord.gg/pkFRvqB)
Minestom is a complete rewrite of Minecraft server software, open-source and without any code from Mojang.
The main difference compared to it 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.
The main difference compared to it is that our implementation of the Minecraft server does not contain any features by default!
However, we have a complete API which allows you to make anything possible with extensions, similar to plugins and mods.
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 Bukkit/Forge/Sponge with this will **not** work since we do not implement any of their APIs.
# Table of contents
- [Install](#install)
@ -23,7 +25,7 @@ This is a developer API not meant to be used by the end-users. Replacing Spigot/
- [License](#license)
# Install
Minestom is similar to Bukkit in the fact that it is not a standlone program, it must be expanded upon.
Minestom is similar to Bukkit in the fact that it is not a standalone program, it must be expanded upon.
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).
@ -31,11 +33,11 @@ This means you need to add Minestom as a dependency, add your code and compile b
# 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://wiki.minestom.com/).
Alternatively you can check the official [wiki](https://wiki.minestom.com/) or the [javadocs](https://minestom.github.io/Minestom/).
# 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.
The goal is to offer more performance for those who need it, Minecraft being single-threaded is the most important problem for them.
Minecraft has 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 vanilla Minecraft such as survival or creative building.
The goal is to offer more performance for those who need it. Minecraft being single-threaded is the biggest problem for them.
In other words, it makes sense to use Minestom when it takes less time to implement everything you want than removing everything you don't need.
@ -51,7 +53,7 @@ Minestom isn't perfect, our choices make it much better for some cases, worse fo
* No more disgusting NMS
## Disadvantages
* Does not work with Bukkit/Spigot plugins
* Does not work with Bukkit/Forge/Sponge plugins or mods
* Does not work with older clients (using a proxy with ViaBackwards is possible)
* Bad for those who want a vanilla experience
* Longer to develop something playable

67
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@ -0,0 +1,67 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '27 19 * * 3'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: [ 'java' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@ -39,11 +39,26 @@ allprojects {
name 'sponge'
url 'https://repo.spongepowered.org/maven'
}
maven {
url "https://repo.velocitypowered.com/snapshots/"
}
}
javadoc {
options {
destinationDir(file("docs"))
addBooleanOption('html5', true)
links "https://jd.adventure.kyori.net/api/$adventureVersion/"
links "https://docs.oracle.com/en/java/javase/11/docs/api/"
}
// see https://stackoverflow.com/a/56641766
doLast {
// Append the fix to the file
def searchScript = new File(destinationDir.getAbsolutePath() + '/search.js')
searchScript.append '\n\n' +
'getURLPrefix = function(ui) {\n' +
' return \'\';\n' +
'};\n'
}
}
@ -109,17 +124,17 @@ dependencies {
testCompileOnly "org.mockito:mockito-core:2.28.2"
// Netty
api 'io.netty:netty-handler:4.1.59.Final'
api 'io.netty:netty-codec:4.1.59.Final'
api 'io.netty:netty-transport-native-epoll:4.1.59.Final:linux-x86_64'
api 'io.netty:netty-transport-native-kqueue:4.1.59.Final:osx-x86_64'
api 'io.netty:netty-handler:4.1.63.Final'
api 'io.netty:netty-codec:4.1.63.Final'
api 'io.netty:netty-transport-native-epoll:4.1.63.Final:linux-x86_64'
api 'io.netty:netty-transport-native-kqueue:4.1.63.Final:osx-x86_64'
api 'io.netty.incubator:netty-incubator-transport-native-io_uring:0.0.4.Final:linux-x86_64'
// https://mvnrepository.com/artifact/org.apache.commons/commons-text
compile group: 'org.apache.commons', name: 'commons-text', version: '1.9'
// https://mvnrepository.com/artifact/it.unimi.dsi/fastutil
api 'it.unimi.dsi:fastutil:8.5.2'
api 'it.unimi.dsi:fastutil:8.5.4'
// https://mvnrepository.com/artifact/com.google.code.gson/gson
api 'com.google.code.gson:gson:2.8.6'
@ -129,7 +144,7 @@ dependencies {
api 'com.github.Articdive:Jnoise:1.0.0'
// https://mvnrepository.com/artifact/org.rocksdb/rocksdbjni
api 'org.rocksdb:rocksdbjni:6.15.2'
api 'org.rocksdb:rocksdbjni:6.16.4'
// Logging
api 'org.apache.logging.log4j:log4j-core:2.14.0'
@ -140,7 +155,7 @@ dependencies {
implementation group: 'org.jline', name: 'jline', version: '3.19.0'
// Guava 21.0+ required for Mixin, but Authlib imports 17.0
api 'com.google.guava:guava:21.0'
api 'com.google.guava:guava:30.1-jre'
api 'com.mojang:authlib:1.5.21'
// Code modification
@ -151,8 +166,11 @@ dependencies {
api "org.ow2.asm:asm-commons:${asmVersion}"
api "org.spongepowered:mixin:${mixinVersion}"
// Compression
implementation "com.velocitypowered:velocity-native:1.1.0-SNAPSHOT"
// Path finding
api 'com.github.MadMartian:hydrazine-path-finding:1.5.4'
api 'com.github.MadMartian:hydrazine-path-finding:1.6.0'
api "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${project.kotlinVersion}"
api "org.jetbrains.kotlin:kotlin-reflect:${project.kotlinVersion}"
@ -168,6 +186,12 @@ dependencies {
api "com.github.Minestom:DependencyGetter:v1.0.1"
// Adventure, for user-interface
api "net.kyori:adventure-api:$adventureVersion"
api "net.kyori:adventure-text-serializer-gson:$adventureVersion"
api "net.kyori:adventure-text-serializer-plain:$adventureVersion"
api "net.kyori:adventure-text-serializer-legacy:$adventureVersion"
// LWJGL, for map rendering
lwjglApi platform("org.lwjgl:lwjgl-bom:$lwjglVersion")
@ -187,6 +211,11 @@ dependencies {
generatorsImplementation("com.squareup:javapoet:1.13.0")
}
configurations.all {
// we use jetbrains annotations
exclude group: "org.checkerframework", module: "checker-qual"
}
publishing {
publications {
mavenJava(MavenPublication) {

View File

@ -1,4 +1,5 @@
asmVersion=9.0
mixinVersion=0.8.1
hephaistosVersion=v1.1.8
kotlinVersion=1.4.21
kotlinVersion=1.4.21
adventureVersion=4.7.0

View File

@ -1,6 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=fd591a34af7385730970399f473afabdb8b28d57fd97d6625c388d090039d6fd
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

@ -1 +1 @@
Subproject commit 424c41d75e375c749640d608b9a3ae3c9592a25e
Subproject commit 2b25cb297bdad56a0673ab54f2a631a9808ea3b6

View File

@ -1,29 +1,11 @@
package net.minestom.server.entity;
import java.util.function.BiFunction;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.key.Keyed;
import net.minestom.server.entity.metadata.EntityMeta;
import net.minestom.server.entity.metadata.PlayerMeta;
import net.minestom.server.entity.metadata.ambient.BatMeta;
import net.minestom.server.entity.metadata.animal.BeeMeta;
import net.minestom.server.entity.metadata.animal.ChickenMeta;
import net.minestom.server.entity.metadata.animal.CowMeta;
import net.minestom.server.entity.metadata.animal.DonkeyMeta;
import net.minestom.server.entity.metadata.animal.FoxMeta;
import net.minestom.server.entity.metadata.animal.HoglinMeta;
import net.minestom.server.entity.metadata.animal.HorseMeta;
import net.minestom.server.entity.metadata.animal.LlamaMeta;
import net.minestom.server.entity.metadata.animal.MooshroomMeta;
import net.minestom.server.entity.metadata.animal.MuleMeta;
import net.minestom.server.entity.metadata.animal.OcelotMeta;
import net.minestom.server.entity.metadata.animal.PandaMeta;
import net.minestom.server.entity.metadata.animal.PigMeta;
import net.minestom.server.entity.metadata.animal.PolarBearMeta;
import net.minestom.server.entity.metadata.animal.RabbitMeta;
import net.minestom.server.entity.metadata.animal.SheepMeta;
import net.minestom.server.entity.metadata.animal.SkeletonHorseMeta;
import net.minestom.server.entity.metadata.animal.StriderMeta;
import net.minestom.server.entity.metadata.animal.TurtleMeta;
import net.minestom.server.entity.metadata.animal.ZombieHorseMeta;
import net.minestom.server.entity.metadata.animal.*;
import net.minestom.server.entity.metadata.animal.tameable.CatMeta;
import net.minestom.server.entity.metadata.animal.tameable.ParrotMeta;
import net.minestom.server.entity.metadata.animal.tameable.WolfMeta;
@ -35,73 +17,15 @@ import net.minestom.server.entity.metadata.flying.PhantomMeta;
import net.minestom.server.entity.metadata.golem.IronGolemMeta;
import net.minestom.server.entity.metadata.golem.ShulkerMeta;
import net.minestom.server.entity.metadata.golem.SnowGolemMeta;
import net.minestom.server.entity.metadata.item.EyeOfEnderMeta;
import net.minestom.server.entity.metadata.item.FireballMeta;
import net.minestom.server.entity.metadata.item.ItemEntityMeta;
import net.minestom.server.entity.metadata.item.SmallFireballMeta;
import net.minestom.server.entity.metadata.item.SnowballMeta;
import net.minestom.server.entity.metadata.item.ThrownEggMeta;
import net.minestom.server.entity.metadata.item.ThrownEnderPearlMeta;
import net.minestom.server.entity.metadata.item.ThrownExperienceBottleMeta;
import net.minestom.server.entity.metadata.item.ThrownPotionMeta;
import net.minestom.server.entity.metadata.minecart.ChestMinecartMeta;
import net.minestom.server.entity.metadata.minecart.CommandBlockMinecartMeta;
import net.minestom.server.entity.metadata.minecart.FurnaceMinecartMeta;
import net.minestom.server.entity.metadata.minecart.HopperMinecartMeta;
import net.minestom.server.entity.metadata.minecart.MinecartMeta;
import net.minestom.server.entity.metadata.minecart.SpawnerMinecartMeta;
import net.minestom.server.entity.metadata.minecart.TntMinecartMeta;
import net.minestom.server.entity.metadata.monster.BlazeMeta;
import net.minestom.server.entity.metadata.monster.CaveSpiderMeta;
import net.minestom.server.entity.metadata.monster.CreeperMeta;
import net.minestom.server.entity.metadata.monster.ElderGuardianMeta;
import net.minestom.server.entity.metadata.monster.EndermanMeta;
import net.minestom.server.entity.metadata.monster.EndermiteMeta;
import net.minestom.server.entity.metadata.monster.GiantMeta;
import net.minestom.server.entity.metadata.monster.GuardianMeta;
import net.minestom.server.entity.metadata.monster.PiglinBruteMeta;
import net.minestom.server.entity.metadata.monster.PiglinMeta;
import net.minestom.server.entity.metadata.monster.SilverfishMeta;
import net.minestom.server.entity.metadata.monster.SpiderMeta;
import net.minestom.server.entity.metadata.monster.VexMeta;
import net.minestom.server.entity.metadata.monster.WitherMeta;
import net.minestom.server.entity.metadata.monster.ZoglinMeta;
import net.minestom.server.entity.metadata.monster.raider.EvokerMeta;
import net.minestom.server.entity.metadata.monster.raider.IllusionerMeta;
import net.minestom.server.entity.metadata.monster.raider.PillagerMeta;
import net.minestom.server.entity.metadata.monster.raider.RavagerMeta;
import net.minestom.server.entity.metadata.monster.raider.VindicatorMeta;
import net.minestom.server.entity.metadata.monster.raider.WitchMeta;
import net.minestom.server.entity.metadata.item.*;
import net.minestom.server.entity.metadata.minecart.*;
import net.minestom.server.entity.metadata.monster.*;
import net.minestom.server.entity.metadata.monster.raider.*;
import net.minestom.server.entity.metadata.monster.skeleton.SkeletonMeta;
import net.minestom.server.entity.metadata.monster.skeleton.StrayMeta;
import net.minestom.server.entity.metadata.monster.skeleton.WitherSkeletonMeta;
import net.minestom.server.entity.metadata.monster.zombie.DrownedMeta;
import net.minestom.server.entity.metadata.monster.zombie.HuskMeta;
import net.minestom.server.entity.metadata.monster.zombie.ZombieMeta;
import net.minestom.server.entity.metadata.monster.zombie.ZombieVillagerMeta;
import net.minestom.server.entity.metadata.monster.zombie.ZombifiedPiglinMeta;
import net.minestom.server.entity.metadata.other.AreaEffectCloudMeta;
import net.minestom.server.entity.metadata.other.ArmorStandMeta;
import net.minestom.server.entity.metadata.other.BoatMeta;
import net.minestom.server.entity.metadata.other.DragonFireballMeta;
import net.minestom.server.entity.metadata.other.EndCrystalMeta;
import net.minestom.server.entity.metadata.other.EnderDragonMeta;
import net.minestom.server.entity.metadata.other.EvokerFangsMeta;
import net.minestom.server.entity.metadata.other.ExperienceOrbMeta;
import net.minestom.server.entity.metadata.other.FallingBlockMeta;
import net.minestom.server.entity.metadata.other.FireworkRocketMeta;
import net.minestom.server.entity.metadata.other.FishingHookMeta;
import net.minestom.server.entity.metadata.other.ItemFrameMeta;
import net.minestom.server.entity.metadata.other.LeashKnotMeta;
import net.minestom.server.entity.metadata.other.LightningBoltMeta;
import net.minestom.server.entity.metadata.other.LlamaSpitMeta;
import net.minestom.server.entity.metadata.other.MagmaCubeMeta;
import net.minestom.server.entity.metadata.other.PaintingMeta;
import net.minestom.server.entity.metadata.other.PrimedTntMeta;
import net.minestom.server.entity.metadata.other.ShulkerBulletMeta;
import net.minestom.server.entity.metadata.other.SlimeMeta;
import net.minestom.server.entity.metadata.other.TraderLlamaMeta;
import net.minestom.server.entity.metadata.other.WitherSkullMeta;
import net.minestom.server.entity.metadata.monster.zombie.*;
import net.minestom.server.entity.metadata.other.*;
import net.minestom.server.entity.metadata.villager.VillagerMeta;
import net.minestom.server.entity.metadata.villager.WanderingTraderMeta;
import net.minestom.server.entity.metadata.water.DolphinMeta;
@ -114,13 +38,15 @@ import net.minestom.server.registry.Registries;
import net.minestom.server.utils.NamespaceID;
import org.jetbrains.annotations.NotNull;
import java.util.function.BiFunction;
/**
* //==============================
* // AUTOGENERATED BY EnumGenerator
* //==============================
*/
@SuppressWarnings({"deprecation"})
public enum EntityType {
public enum EntityType implements Keyed {
AREA_EFFECT_CLOUD("minecraft:area_effect_cloud", 6.0, 0.5, AreaEffectCloudMeta::new, EntitySpawnType.BASE),
ARMOR_STAND("minecraft:armor_stand", 0.5, 1.975, ArmorStandMeta::new, EntitySpawnType.LIVING),
@ -352,6 +278,8 @@ public enum EntityType {
@NotNull
private final EntitySpawnType spawnType;
private final Key key;
EntityType(@NotNull String namespaceID, double width, double height,
@NotNull BiFunction<Entity, Metadata, EntityMeta> metaConstructor,
@NotNull EntitySpawnType spawnType) {
@ -361,6 +289,7 @@ public enum EntityType {
this.metaConstructor = metaConstructor;
this.spawnType = spawnType;
Registries.entityTypes.put(NamespaceID.from(namespaceID), this);
this.key = Key.key(this.namespaceID);
}
public short getId() {
@ -393,4 +322,8 @@ public enum EntityType {
}
return null;
}
public Key key() {
return this.key;
}
}

View File

@ -1,5 +1,7 @@
package net.minestom.server.fluids;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.key.Keyed;
import net.minestom.server.registry.Registries;
import net.minestom.server.utils.NamespaceID;
@ -9,7 +11,7 @@ import net.minestom.server.utils.NamespaceID;
* //==============================
*/
@SuppressWarnings({"deprecation"})
public enum Fluid {
public enum Fluid implements Keyed {
EMPTY("minecraft:empty"),
FLOWING_WATER("minecraft:flowing_water"),
@ -20,11 +22,14 @@ public enum Fluid {
LAVA("minecraft:lava");
private String namespaceID;
private final String namespaceID;
private final Key key;
Fluid(String namespaceID) {
this.namespaceID = namespaceID;
Registries.fluids.put(NamespaceID.from(namespaceID), this);
this.key = Key.key(this.namespaceID);
}
public int getId() {
@ -35,6 +40,10 @@ public enum Fluid {
return namespaceID;
}
public Key key() {
return this.key;
}
public static Fluid fromId(int id) {
if (id >= 0 && id < values().length) {
return values()[id];

View File

@ -2,6 +2,8 @@ package net.minestom.server.instance.block;
import java.util.Arrays;
import java.util.List;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.key.Keyed;
import net.minestom.server.instance.block.states.AcaciaButton;
import net.minestom.server.instance.block.states.AcaciaDoor;
import net.minestom.server.instance.block.states.AcaciaFence;
@ -481,7 +483,7 @@ import org.jetbrains.annotations.Nullable;
* //==============================
*/
@SuppressWarnings({"deprecation"})
public enum Block {
public enum Block implements Keyed {
AIR("minecraft:air", (short) 0, 0.0, 0.0, true, false, null, true),
STONE("minecraft:stone", (short) 1, 1.5, 6.0, false, true, null, true),
@ -2480,25 +2482,27 @@ public enum Block {
}
@NotNull
private String namespaceID;
private final String namespaceID;
private short defaultID;
private final short defaultID;
private double hardness;
private final double hardness;
private double resistance;
private final double resistance;
private boolean isAir;
private final boolean isAir;
private boolean isSolid;
private final boolean isSolid;
@Nullable
private NamespaceID blockEntity;
private final NamespaceID blockEntity;
private boolean singleState;
private final boolean singleState;
private List<BlockAlternative> alternatives = new java.util.ArrayList<>();
private final Key key;
Block(@NotNull String namespaceID, short defaultID, double hardness, double resistance,
boolean isAir, boolean isSolid, @Nullable NamespaceID blockEntity,
boolean singleState) {
@ -2514,6 +2518,7 @@ public enum Block {
addBlockAlternative(new BlockAlternative(defaultID));
}
Registries.blocks.put(NamespaceID.from(namespaceID), this);
this.key = Key.key(this.namespaceID);
}
public short getBlockId() {
@ -2586,4 +2591,8 @@ public enum Block {
public static Block fromStateId(short blockStateId) {
return BlockArray.blocks[blockStateId];
}
public Key key() {
return this.key;
}
}

View File

@ -1,5 +1,7 @@
package net.minestom.server.item;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.key.Keyed;
import net.minestom.server.registry.Registries;
import net.minestom.server.utils.NamespaceID;
@ -9,7 +11,7 @@ import net.minestom.server.utils.NamespaceID;
* //==============================
*/
@SuppressWarnings({"deprecation"})
public enum Enchantment {
public enum Enchantment implements Keyed {
PROTECTION("minecraft:protection"),
FIRE_PROTECTION("minecraft:fire_protection"),
@ -86,11 +88,14 @@ public enum Enchantment {
VANISHING_CURSE("minecraft:vanishing_curse");
private String namespaceID;
private final String namespaceID;
private final Key key;
Enchantment(String namespaceID) {
this.namespaceID = namespaceID;
Registries.enchantments.put(NamespaceID.from(namespaceID), this);
this.key = Key.key(this.namespaceID);
}
public int getId() {
@ -101,6 +106,10 @@ public enum Enchantment {
return namespaceID;
}
public Key key() {
return this.key;
}
public static Enchantment fromId(int id) {
if (id >= 0 && id < values().length) {
return values()[id];

View File

@ -1,5 +1,7 @@
package net.minestom.server.item;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.key.Keyed;
import net.minestom.server.instance.block.Block;
import net.minestom.server.registry.Registries;
import net.minestom.server.utils.NamespaceID;
@ -12,7 +14,7 @@ import org.jetbrains.annotations.Nullable;
* //==============================
*/
@SuppressWarnings({"deprecation"})
public enum Material {
public enum Material implements Keyed {
AIR("minecraft:air", 64, Block.AIR),
STONE("minecraft:stone", 64, Block.STONE),
@ -1966,12 +1968,14 @@ public enum Material {
RESPAWN_ANCHOR("minecraft:respawn_anchor", 64, Block.RESPAWN_ANCHOR);
@NotNull
private String namespaceID;
private final String namespaceID;
private int maxDefaultStackSize;
private final int maxDefaultStackSize;
@Nullable
private Block correspondingBlock;
private final Block correspondingBlock;
private final Key key;
Material(@NotNull String namespaceID, int maxDefaultStackSize,
@Nullable Block correspondingBlock) {
@ -1979,6 +1983,7 @@ public enum Material {
this.maxDefaultStackSize = maxDefaultStackSize;
this.correspondingBlock = correspondingBlock;
Registries.materials.put(NamespaceID.from(namespaceID), this);
this.key = Key.key(this.namespaceID);
}
public short getId() {
@ -2083,4 +2088,8 @@ public enum Material {
}
return isFood();
}
public Key key() {
return this.key;
}
}

View File

@ -1,5 +1,7 @@
package net.minestom.server.particle;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.key.Keyed;
import net.minestom.server.registry.Registries;
import net.minestom.server.utils.NamespaceID;
@ -9,7 +11,7 @@ import net.minestom.server.utils.NamespaceID;
* //==============================
*/
@SuppressWarnings({"deprecation"})
public enum Particle {
public enum Particle implements Keyed {
AMBIENT_ENTITY_EFFECT("minecraft:ambient_entity_effect"),
ANGRY_VILLAGER("minecraft:angry_villager"),
@ -154,11 +156,14 @@ public enum Particle {
WHITE_ASH("minecraft:white_ash");
private String namespaceID;
private final String namespaceID;
private final Key key;
Particle(String namespaceID) {
this.namespaceID = namespaceID;
Registries.particles.put(NamespaceID.from(namespaceID), this);
this.key = Key.key(this.namespaceID);
}
public int getId() {
@ -169,6 +174,10 @@ public enum Particle {
return namespaceID;
}
public Key key() {
return this.key;
}
public static Particle fromId(int id) {
if (id >= 0 && id < values().length) {
return values()[id];

View File

@ -1,5 +1,7 @@
package net.minestom.server.potion;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.key.Keyed;
import net.minestom.server.registry.Registries;
import net.minestom.server.utils.NamespaceID;
@ -9,7 +11,7 @@ import net.minestom.server.utils.NamespaceID;
* //==============================
*/
@SuppressWarnings({"deprecation"})
public enum PotionEffect {
public enum PotionEffect implements Keyed {
SPEED("minecraft:speed"),
SLOWNESS("minecraft:slowness"),
@ -74,11 +76,14 @@ public enum PotionEffect {
HERO_OF_THE_VILLAGE("minecraft:hero_of_the_village");
private String namespaceID;
private final String namespaceID;
private final Key key;
PotionEffect(String namespaceID) {
this.namespaceID = namespaceID;
Registries.potionEffects.put(NamespaceID.from(namespaceID), this);
this.key = Key.key(this.namespaceID);
}
public int getId() {
@ -89,6 +94,10 @@ public enum PotionEffect {
return namespaceID;
}
public Key key() {
return this.key;
}
public static PotionEffect fromId(int id) {
if (id >= 0 && id < values().length + 1) {
return values()[id - 1];

View File

@ -1,5 +1,7 @@
package net.minestom.server.potion;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.key.Keyed;
import net.minestom.server.registry.Registries;
import net.minestom.server.utils.NamespaceID;
@ -9,7 +11,7 @@ import net.minestom.server.utils.NamespaceID;
* //==============================
*/
@SuppressWarnings({"deprecation"})
public enum PotionType {
public enum PotionType implements Keyed {
EMPTY("minecraft:empty"),
WATER("minecraft:water"),
@ -96,11 +98,14 @@ public enum PotionType {
LONG_SLOW_FALLING("minecraft:long_slow_falling");
private String namespaceID;
private final String namespaceID;
private final Key key;
PotionType(String namespaceID) {
this.namespaceID = namespaceID;
Registries.potionTypes.put(NamespaceID.from(namespaceID), this);
this.key = Key.key(this.namespaceID);
}
public int getId() {
@ -111,6 +116,10 @@ public enum PotionType {
return namespaceID;
}
public Key key() {
return this.key;
}
public static PotionType fromId(int id) {
if (id >= 0 && id < values().length) {
return values()[id];

View File

@ -2,6 +2,7 @@
package net.minestom.server.registry;
import java.util.HashMap;
import net.kyori.adventure.key.Key;
import net.minestom.server.entity.EntityType;
import net.minestom.server.fluids.Fluid;
import net.minestom.server.instance.block.Block;
@ -10,7 +11,7 @@ import net.minestom.server.item.Material;
import net.minestom.server.particle.Particle;
import net.minestom.server.potion.PotionEffect;
import net.minestom.server.potion.PotionType;
import net.minestom.server.sound.Sound;
import net.minestom.server.sound.SoundEvent;
import net.minestom.server.stat.StatisticType;
import net.minestom.server.utils.NamespaceID;
import org.jetbrains.annotations.NotNull;
@ -66,7 +67,7 @@ public final class Registries {
* Should only be used for internal code, please use the get* methods.
*/
@Deprecated
public static final HashMap<NamespaceID, Sound> sounds = new HashMap<>();
public static final HashMap<NamespaceID, SoundEvent> soundEvents = new HashMap<>();
/**
* Should only be used for internal code, please use the get* methods.
@ -96,6 +97,14 @@ public final class Registries {
return blocks.getOrDefault(id, Block.AIR);
}
/**
* Returns the corresponding Block matching the given key. Returns 'AIR' if none match.
*/
@NotNull
public static Block getBlock(Key key) {
return getBlock(NamespaceID.from(key));
}
/**
* Returns the corresponding Material matching the given id. Returns 'AIR' if none match.
*/
@ -112,6 +121,14 @@ public final class Registries {
return materials.getOrDefault(id, Material.AIR);
}
/**
* Returns the corresponding Material matching the given key. Returns 'AIR' if none match.
*/
@NotNull
public static Material getMaterial(Key key) {
return getMaterial(NamespaceID.from(key));
}
/**
* Returns the corresponding Enchantment matching the given id. Returns null if none match.
*/
@ -128,6 +145,14 @@ public final class Registries {
return enchantments.get(id);
}
/**
* Returns the corresponding Enchantment matching the given key. Returns null if none match.
*/
@Nullable
public static Enchantment getEnchantment(Key key) {
return getEnchantment(NamespaceID.from(key));
}
/**
* Returns the corresponding EntityType matching the given id. Returns null if none match.
*/
@ -144,6 +169,14 @@ public final class Registries {
return entityTypes.get(id);
}
/**
* Returns the corresponding EntityType matching the given key. Returns null if none match.
*/
@Nullable
public static EntityType getEntityType(Key key) {
return getEntityType(NamespaceID.from(key));
}
/**
* Returns the corresponding Particle matching the given id. Returns null if none match.
*/
@ -160,6 +193,14 @@ public final class Registries {
return particles.get(id);
}
/**
* Returns the corresponding Particle matching the given key. Returns null if none match.
*/
@Nullable
public static Particle getParticle(Key key) {
return getParticle(NamespaceID.from(key));
}
/**
* Returns the corresponding PotionType matching the given id. Returns null if none match.
*/
@ -176,6 +217,14 @@ public final class Registries {
return potionTypes.get(id);
}
/**
* Returns the corresponding PotionType matching the given key. Returns null if none match.
*/
@Nullable
public static PotionType getPotionType(Key key) {
return getPotionType(NamespaceID.from(key));
}
/**
* Returns the corresponding PotionEffect matching the given id. Returns null if none match.
*/
@ -193,19 +242,35 @@ public final class Registries {
}
/**
* Returns the corresponding Sound matching the given id. Returns null if none match.
* Returns the corresponding PotionEffect matching the given key. Returns null if none match.
*/
@Nullable
public static Sound getSound(String id) {
return getSound(NamespaceID.from(id));
public static PotionEffect getPotionEffect(Key key) {
return getPotionEffect(NamespaceID.from(key));
}
/**
* Returns the corresponding Sound matching the given id. Returns null if none match.
* Returns the corresponding SoundEvent matching the given id. Returns null if none match.
*/
@Nullable
public static Sound getSound(NamespaceID id) {
return sounds.get(id);
public static SoundEvent getSoundEvent(String id) {
return getSoundEvent(NamespaceID.from(id));
}
/**
* Returns the corresponding SoundEvent matching the given id. Returns null if none match.
*/
@Nullable
public static SoundEvent getSoundEvent(NamespaceID id) {
return soundEvents.get(id);
}
/**
* Returns the corresponding SoundEvent matching the given key. Returns null if none match.
*/
@Nullable
public static SoundEvent getSoundEvent(Key key) {
return getSoundEvent(NamespaceID.from(key));
}
/**
@ -224,6 +289,14 @@ public final class Registries {
return statisticTypes.get(id);
}
/**
* Returns the corresponding StatisticType matching the given key. Returns null if none match.
*/
@Nullable
public static StatisticType getStatisticType(Key key) {
return getStatisticType(NamespaceID.from(key));
}
/**
* Returns the corresponding Fluid matching the given id. Returns 'EMPTY' if none match.
*/
@ -239,4 +312,12 @@ public final class Registries {
public static Fluid getFluid(NamespaceID id) {
return fluids.getOrDefault(id, Fluid.EMPTY);
}
/**
* Returns the corresponding Fluid matching the given key. Returns 'EMPTY' if none match.
*/
@NotNull
public static Fluid getFluid(Key key) {
return getFluid(NamespaceID.from(key));
}
}

View File

@ -1,5 +1,8 @@
package net.minestom.server.sound;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.key.Keyed;
import net.kyori.adventure.sound.Sound;
import net.minestom.server.registry.Registries;
import net.minestom.server.utils.NamespaceID;
@ -9,7 +12,7 @@ import net.minestom.server.utils.NamespaceID;
* //==============================
*/
@SuppressWarnings({"deprecation"})
public enum Sound {
public enum SoundEvent implements Keyed, Sound.Type {
AMBIENT_CAVE("minecraft:ambient.cave"),
AMBIENT_BASALT_DELTAS_ADDITIONS("minecraft:ambient.basalt_deltas.additions"),
@ -1994,11 +1997,14 @@ public enum Sound {
ENTITY_ZOMBIE_VILLAGER_STEP("minecraft:entity.zombie_villager.step");
private String namespaceID;
private final String namespaceID;
Sound(String namespaceID) {
private final Key key;
SoundEvent(String namespaceID) {
this.namespaceID = namespaceID;
Registries.sounds.put(NamespaceID.from(namespaceID), this);
Registries.soundEvents.put(NamespaceID.from(namespaceID), this);
this.key = Key.key(this.namespaceID);
}
public int getId() {
@ -2009,7 +2015,11 @@ public enum Sound {
return namespaceID;
}
public static Sound fromId(int id) {
public Key key() {
return this.key;
}
public static SoundEvent fromId(int id) {
if (id >= 0 && id < values().length) {
return values()[id];
}

View File

@ -1,5 +1,7 @@
package net.minestom.server.stat;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.key.Keyed;
import net.minestom.server.registry.Registries;
import net.minestom.server.utils.NamespaceID;
@ -9,7 +11,7 @@ import net.minestom.server.utils.NamespaceID;
* //==============================
*/
@SuppressWarnings({"deprecation"})
public enum StatisticType {
public enum StatisticType implements Keyed {
LEAVE_GAME("minecraft:leave_game"),
PLAY_ONE_MINUTE("minecraft:play_one_minute"),
@ -158,11 +160,14 @@ public enum StatisticType {
INTERACT_WITH_SMITHING_TABLE("minecraft:interact_with_smithing_table");
private String namespaceID;
private final String namespaceID;
private final Key key;
StatisticType(String namespaceID) {
this.namespaceID = namespaceID;
Registries.statisticTypes.put(NamespaceID.from(namespaceID), this);
this.key = Key.key(this.namespaceID);
}
public int getId() {
@ -173,6 +178,10 @@ public enum StatisticType {
return namespaceID;
}
public Key key() {
return this.key;
}
public static StatisticType fromId(int id) {
if (id >= 0 && id < values().length) {
return values()[id];

View File

@ -3,6 +3,8 @@ package net.minestom.codegen;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.squareup.javapoet.*;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.key.Keyed;
import net.minestom.server.registry.Registries;
import net.minestom.server.utils.NamespaceID;
@ -116,6 +118,12 @@ public abstract class BasicEnumGenerator extends MinestomEnumGenerator<BasicEnum
generator.appendToConstructor(code -> {
code.addStatement("$T." + CodeGenerator.decapitalize(getClassName()) + "s.put($T.from($N), this)", registriesClass, NamespaceID.class, "namespaceID");
});
// implement Keyed
generator.addSuperinterface(ClassName.get(Keyed.class));
generator.addField(ClassName.get(Key.class), "key", true);
generator.appendToConstructor(code -> code.addStatement("this.key = Key.key(this.namespaceID)"));
generator.addMethod("key", new ParameterSpec[0], ClassName.get(Key.class), code -> code.addStatement("return this.key"));
}
@Override

View File

@ -1,6 +1,7 @@
package net.minestom.codegen;
import com.squareup.javapoet.*;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -20,9 +21,11 @@ public class EnumGenerator implements CodeGenerator {
private final String enumName;
private ParameterSpec[] parameters;
private List<TypeName> superinterfaces = new LinkedList<>();
private List<Method> methods = new LinkedList<>();
private List<Field> fields = new LinkedList<>();
private List<Field> staticFields = new LinkedList<>();
private List<Instance> instances = new LinkedList<>();
private List<Pair<Field, Boolean>> fields = new LinkedList<>();
private List<Field> hardcodedFields = new LinkedList<>();
private List<AnnotationSpec> annotations = new LinkedList<>();
private String enumPackage;
@ -35,6 +38,10 @@ public class EnumGenerator implements CodeGenerator {
this.enumName = enumName;
}
public void addSuperinterface(TypeName typeNames) {
superinterfaces.add(typeNames);
}
public void setParams(ParameterSpec... parameters) {
this.parameters = parameters;
}
@ -52,7 +59,7 @@ public class EnumGenerator implements CodeGenerator {
}
public void addStaticField(TypeName type, String name, String value) {
fields.add(new Field(type, name, value));
staticFields.add(new Field(type, name, value));
}
public void addInstance(String name, Object... parameters) {
@ -86,6 +93,9 @@ public class EnumGenerator implements CodeGenerator {
enumClass.addEnumConstant(instance.name, arguments);
}
// add superinterfaces
enumClass.addSuperinterfaces(superinterfaces);
if (staticBlock != null) {
enumClass.addStaticBlock(staticBlock);
}
@ -100,7 +110,7 @@ public class EnumGenerator implements CodeGenerator {
.build());
}
for (Field field : fields) {
for (Field field : staticFields) {
enumClass.addField(FieldSpec.builder(field.type, field.name)
.initializer("$L", field.value)
.addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC)
@ -115,10 +125,21 @@ public class EnumGenerator implements CodeGenerator {
.build());
}
// normal fields
for (Pair<Field, Boolean> field : fields) {
FieldSpec.Builder builder = FieldSpec.builder(field.getLeft().type, field.getLeft().name)
.addModifiers(Modifier.PRIVATE);
if (field.getRight()) {
builder.addModifiers(Modifier.FINAL);
}
enumClass.addField(builder.build());
}
// constructor
MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder();
for (int i = 0; i < parameters.length; i++) {
ParameterSpec param = parameters[i];
for (ParameterSpec param : parameters) {
constructorBuilder.addParameter(param);
// property assignment
@ -166,6 +187,10 @@ public class EnumGenerator implements CodeGenerator {
constructorEnds.add(constructorEnding);
}
public void addField(TypeName type, String name, boolean isFinal) {
fields.add(Pair.of(new Field(type, name), isFinal));
}
public void addHardcodedField(TypeName type, String name, String value) {
hardcodedFields.add(new Field(type, name, value));
}
@ -210,6 +235,10 @@ public class EnumGenerator implements CodeGenerator {
private String name;
private String value;
public Field(TypeName type, String name) {
this(type, name, null);
}
public Field(TypeName type, String name, String value) {
this.type = type;
this.name = name;

View File

@ -1,6 +1,7 @@
package net.minestom.codegen;
import com.squareup.javapoet.*;
import net.kyori.adventure.key.Key;
import net.minestom.server.entity.EntityType;
import net.minestom.server.fluids.Fluid;
import net.minestom.server.instance.block.Block;
@ -10,7 +11,7 @@ import net.minestom.server.particle.Particle;
import net.minestom.server.potion.PotionEffect;
import net.minestom.server.potion.PotionType;
import net.minestom.server.registry.ResourceGatherer;
import net.minestom.server.sound.Sound;
import net.minestom.server.sound.SoundEvent;
import net.minestom.server.stat.StatisticType;
import net.minestom.server.utils.NamespaceID;
import org.apache.commons.lang3.tuple.ImmutablePair;
@ -44,7 +45,7 @@ public class RegistriesGenerator implements CodeGenerator {
new ImmutablePair<>(Particle.class.getCanonicalName(), null),
new ImmutablePair<>(PotionType.class.getCanonicalName(), null),
new ImmutablePair<>(PotionEffect.class.getCanonicalName(), null),
new ImmutablePair<>(Sound.class.getCanonicalName(), null),
new ImmutablePair<>(SoundEvent.class.getCanonicalName(), null),
new ImmutablePair<>(StatisticType.class.getCanonicalName(), null),
new ImmutablePair<>(Fluid.class.getCanonicalName(), "EMPTY"),
};
@ -101,6 +102,7 @@ public class RegistriesGenerator implements CodeGenerator {
ParameterSpec namespaceIDParam = ParameterSpec.builder(ClassName.get(NamespaceID.class), "id")
.build();
ParameterSpec keyIDParam = ParameterSpec.builder(ClassName.get(Key.class), "key").build();
CodeBlock.Builder code = CodeBlock.builder();
Class<? extends Annotation> annotation;
@ -134,6 +136,16 @@ public class RegistriesGenerator implements CodeGenerator {
.addCode(code.build())
.addJavadoc(comment.toString())
.build());
// Key variant
registriesClass.addMethod(MethodSpec.methodBuilder("get" + simpleType)
.returns(type)
.addAnnotation(annotation)
.addModifiers(Modifier.STATIC, Modifier.PUBLIC)
.addParameter(keyIDParam)
.addStatement("return get$N(NamespaceID.from($N))", simpleType, keyIDParam)
.addJavadoc(comment.toString().replace(" id.", " key."))
.build());
}
JavaFile file = JavaFile.builder("net.minestom.server.registry", registriesClass.build())

View File

@ -5,6 +5,8 @@ import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.squareup.javapoet.*;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.key.Keyed;
import net.minestom.codegen.EnumGenerator;
import net.minestom.codegen.MinestomEnumGenerator;
import net.minestom.codegen.PrismarinePaths;
@ -298,6 +300,12 @@ public class BlockEnumGenerator extends MinestomEnumGenerator<BlockContainer> {
.endControlFlow()
.addStatement("$T.blocks.put($T.from(namespaceID), this)", Registries.class, NamespaceID.class);
});
// implement Keyed
generator.addSuperinterface(ClassName.get(Keyed.class));
generator.addField(ClassName.get(Key.class), "key", true);
generator.appendToConstructor(code -> code.addStatement("this.key = Key.key(this.namespaceID)"));
generator.addMethod("key", new ParameterSpec[0], ClassName.get(Key.class), code -> code.addStatement("return this.key"));
}
@Override

View File

@ -1,7 +1,6 @@
package net.minestom.codegen.enchantment;
import net.minestom.codegen.BasicEnumGenerator;
import net.minestom.codegen.stats.StatsEnumGenerator;
import net.minestom.server.registry.ResourceGatherer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@ -4,6 +4,8 @@ import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.squareup.javapoet.*;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.key.Keyed;
import net.minestom.codegen.ConstructorLambda;
import net.minestom.codegen.EnumGenerator;
import net.minestom.codegen.MinestomEnumGenerator;
@ -177,6 +179,12 @@ public class EntityTypeEnumGenerator extends MinestomEnumGenerator<EntityTypeCon
.endControlFlow()
.addStatement("return null");
});
// implement Keyed
generator.addSuperinterface(ClassName.get(Keyed.class));
generator.addField(ClassName.get(Key.class), "key", true);
generator.appendToConstructor(code -> code.addStatement("this.key = Key.key(this.namespaceID)"));
generator.addMethod("key", new ParameterSpec[0], ClassName.get(Key.class), code -> code.addStatement("return this.key"));
}
@Override

View File

@ -1,7 +1,6 @@
package net.minestom.codegen.fluids;
import net.minestom.codegen.BasicEnumGenerator;
import net.minestom.codegen.stats.StatsEnumGenerator;
import net.minestom.server.registry.ResourceGatherer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@ -4,6 +4,8 @@ import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.squareup.javapoet.*;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.key.Keyed;
import net.minestom.codegen.EnumGenerator;
import net.minestom.codegen.MinestomEnumGenerator;
import net.minestom.codegen.PrismarinePaths;
@ -218,6 +220,12 @@ public class ItemEnumGenerator extends MinestomEnumGenerator<ItemContainer> {
.endControlFlow()
.addStatement("return isFood()");
});
// implement Keyed
generator.addSuperinterface(ClassName.get(Keyed.class));
generator.addField(ClassName.get(Key.class), "key", true);
generator.appendToConstructor(code -> code.addStatement("this.key = Key.key(this.namespaceID)"));
generator.addMethod("key", new ParameterSpec[0], ClassName.get(Key.class), code -> code.addStatement("return this.key"));
}
@Override

View File

@ -1,7 +1,6 @@
package net.minestom.codegen.potions;
import net.minestom.codegen.BasicEnumGenerator;
import net.minestom.codegen.stats.StatsEnumGenerator;
import net.minestom.server.registry.ResourceGatherer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@ -1,7 +1,9 @@
package net.minestom.codegen.sounds;
import com.squareup.javapoet.ClassName;
import net.kyori.adventure.sound.Sound;
import net.minestom.codegen.BasicEnumGenerator;
import net.minestom.codegen.stats.StatsEnumGenerator;
import net.minestom.codegen.EnumGenerator;
import net.minestom.server.registry.ResourceGatherer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -45,6 +47,14 @@ public class SoundEnumGenerator extends BasicEnumGenerator {
super(targetFolder);
}
@Override
protected void prepare(EnumGenerator generator) {
super.prepare(generator);
// implement type as well
generator.addSuperinterface(ClassName.get(Sound.Type.class));
}
@Override
protected String getCategoryID() {
return "minecraft:sound_event";
@ -57,7 +67,7 @@ public class SoundEnumGenerator extends BasicEnumGenerator {
@Override
public String getClassName() {
return "Sound";
return "SoundEvent";
}
@Override

View File

@ -68,8 +68,9 @@ public class Demo {
private static void createFrame(Instance instance, int id, int x, int y, int z) {
EntityItemFrame itemFrame = new EntityItemFrame(new Position(x, y, z), EntityItemFrame.ItemFrameOrientation.NORTH);
itemFrame.getPosition().setYaw(180f);
ItemStack map = new ItemStack(Material.FILLED_MAP, (byte) 1);
map.setItemMeta(new MapMeta(id));
ItemStack map = ItemStack.builder(Material.FILLED_MAP)
.meta(new MapMeta.Builder().mapId(id).build())
.build();
itemFrame.setItemStack(map);
itemFrame.setInstance(instance);
itemFrame.setCustomNameVisible(true);

View File

@ -12,7 +12,6 @@ import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.util.stream.Collectors;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL20.*;
public final class OpenGLRendering {

View File

@ -7,8 +7,6 @@ import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class PaletteGenerator {

View File

@ -14,7 +14,6 @@ import org.lwjgl.system.MemoryStack;
import java.nio.ByteBuffer;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.glfw.GLFW.glfwTerminate;
import static org.lwjgl.opengl.GL11.*;
public abstract class GLFWCapableBuffer {

View File

@ -167,7 +167,7 @@ public class MapColorRenderer implements Runnable {
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, screenQuadIndices);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
};
}
glUseProgram(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);

View File

@ -1,7 +1,8 @@
package net.minestom.server;
import net.minestom.server.advancements.AdvancementManager;
import net.minestom.server.benchmark.BenchmarkManager;
import net.minestom.server.adventure.bossbar.BossBarManager;
import net.minestom.server.monitoring.BenchmarkManager;
import net.minestom.server.command.CommandManager;
import net.minestom.server.data.DataManager;
import net.minestom.server.data.DataType;
@ -37,7 +38,7 @@ import net.minestom.server.potion.PotionType;
import net.minestom.server.recipe.RecipeManager;
import net.minestom.server.registry.ResourceGatherer;
import net.minestom.server.scoreboard.TeamManager;
import net.minestom.server.sound.Sound;
import net.minestom.server.sound.SoundEvent;
import net.minestom.server.stat.StatisticType;
import net.minestom.server.storage.StorageLocation;
import net.minestom.server.storage.StorageManager;
@ -116,6 +117,7 @@ public final class MinecraftServer {
private static DimensionTypeManager dimensionTypeManager;
private static BiomeManager biomeManager;
private static AdvancementManager advancementManager;
private static BossBarManager bossBarManager;
private static ExtensionManager extensionManager;
@ -159,7 +161,7 @@ public final class MinecraftServer {
PotionEffect.values();
Enchantment.values();
EntityType.values();
Sound.values();
SoundEvent.values();
Particle.values();
StatisticType.values();
Fluid.values();
@ -181,6 +183,7 @@ public final class MinecraftServer {
dimensionTypeManager = new DimensionTypeManager();
biomeManager = new BiomeManager();
advancementManager = new AdvancementManager();
bossBarManager = new BossBarManager();
updateManager = new UpdateManager();
@ -428,6 +431,16 @@ public final class MinecraftServer {
return connectionManager;
}
/**
* Gets the boss bar manager.
*
* @return the boss bar manager
*/
public static BossBarManager getBossBarManager() {
checkInitStatus(bossBarManager);
return bossBarManager;
}
/**
* Gets the object handling the client packets processing.
* <p>
@ -751,7 +764,6 @@ public final class MinecraftServer {
// Load extensions
extensionManager.loadExtensions();
// Init extensions
// TODO: Extensions should handle depending on each other and have a load-order.
extensionManager.getExtensions().forEach(Extension::preInitialize);
extensionManager.getExtensions().forEach(Extension::initialize);
extensionManager.getExtensions().forEach(Extension::postInitialize);
@ -804,5 +816,4 @@ public final class MinecraftServer {
"You cannot access the manager before MinecraftServer#init, " +
"if you are developing an extension be sure to retrieve them at least after Extension#preInitialize");*/
}
}

View File

@ -1,19 +1,21 @@
package net.minestom.server;
import com.google.common.collect.Queues;
import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.InstanceManager;
import net.minestom.server.monitoring.TickMonitor;
import net.minestom.server.network.ConnectionManager;
import net.minestom.server.network.player.NettyPlayerConnection;
import net.minestom.server.thread.PerInstanceThreadProvider;
import net.minestom.server.thread.ThreadProvider;
import net.minestom.server.utils.async.AsyncUtils;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.*;
import java.util.function.Consumer;
import java.util.function.LongConsumer;
/**
@ -24,7 +26,8 @@ import java.util.function.LongConsumer;
*/
public final class UpdateManager {
private final ScheduledExecutorService updateExecutionService = Executors.newSingleThreadScheduledExecutor();
private final ScheduledExecutorService updateExecutionService = Executors.newSingleThreadScheduledExecutor(r ->
new Thread(r, "tick-scheduler"));
private volatile boolean stopRequested;
@ -32,6 +35,7 @@ public final class UpdateManager {
private final Queue<LongConsumer> tickStartCallbacks = Queues.newConcurrentLinkedQueue();
private final Queue<LongConsumer> tickEndCallbacks = Queues.newConcurrentLinkedQueue();
private final List<Consumer<TickMonitor>> tickMonitors = new CopyOnWriteArrayList<>();
{
// DEFAULT THREAD PROVIDER
@ -77,7 +81,20 @@ public final class UpdateManager {
final long tickTime = System.nanoTime() - currentTime;
// Tick end callbacks
doTickCallback(tickEndCallbacks, tickTime / 1000000L);
doTickCallback(tickEndCallbacks, tickTime);
// Monitoring
if (!tickMonitors.isEmpty()) {
final double tickTimeMs = tickTime / 1e6D;
final TickMonitor tickMonitor = new TickMonitor(tickTimeMs);
this.tickMonitors.forEach(consumer -> consumer.accept(tickMonitor));
}
// Flush all waiting packets
AsyncUtils.runAsync(() -> connectionManager.getOnlinePlayers().stream()
.filter(player -> player.getPlayerConnection() instanceof NettyPlayerConnection)
.map(player -> (NettyPlayerConnection) player.getPlayerConnection())
.forEach(NettyPlayerConnection::flush));
} catch (Exception e) {
MinecraftServer.getExceptionManager().handleException(e);
@ -174,13 +191,12 @@ public final class UpdateManager {
* WARNING: should be automatically done by the {@link Instance} implementation.
*
* @param instance the instance of the chunk
* @param chunkX the chunk X
* @param chunkZ the chunk Z
* @param chunk the loaded chunk
*/
public synchronized void signalChunkLoad(Instance instance, int chunkX, int chunkZ) {
public synchronized void signalChunkLoad(Instance instance, @NotNull Chunk chunk) {
if (this.threadProvider == null)
return;
this.threadProvider.onChunkLoad(instance, chunkX, chunkZ);
this.threadProvider.onChunkLoad(instance, chunk);
}
/**
@ -189,13 +205,12 @@ public final class UpdateManager {
* WARNING: should be automatically done by the {@link Instance} implementation.
*
* @param instance the instance of the chunk
* @param chunkX the chunk X
* @param chunkZ the chunk Z
* @param chunk the unloaded chunk
*/
public synchronized void signalChunkUnload(Instance instance, int chunkX, int chunkZ) {
public synchronized void signalChunkUnload(Instance instance, @NotNull Chunk chunk) {
if (this.threadProvider == null)
return;
this.threadProvider.onChunkUnload(instance, chunkX, chunkZ);
this.threadProvider.onChunkUnload(instance, chunk);
}
/**
@ -238,6 +253,14 @@ public final class UpdateManager {
this.tickEndCallbacks.remove(callback);
}
public void addTickMonitor(@NotNull Consumer<TickMonitor> consumer) {
this.tickMonitors.add(consumer);
}
public void removeTickMonitor(@NotNull Consumer<TickMonitor> consumer) {
this.tickMonitors.remove(consumer);
}
/**
* Stops the server loop.
*/

View File

@ -1,5 +1,7 @@
package net.minestom.server;
import net.kyori.adventure.audience.Audience;
import net.minestom.server.adventure.audience.PacketGroupingAudience;
import net.minestom.server.entity.Player;
import net.minestom.server.network.packet.server.ServerPacket;
import net.minestom.server.utils.PacketUtils;
@ -80,9 +82,25 @@ public interface Viewable {
* @param packet the packet to send
*/
default void sendPacketToViewersAndSelf(@NotNull ServerPacket packet) {
if (this instanceof Player) {
((Player) this).getPlayerConnection().sendPacket(packet);
}
sendPacketToViewers(packet);
}
/**
* Gets the result of {@link #getViewers()} as an Adventure Audience.
*
* @return the audience
*/
default @NotNull Audience getViewersAsAudience() {
return PacketGroupingAudience.of(this.getViewers());
}
/**
* Gets the result of {@link #getViewers()} as an {@link Iterable} of Adventure
* {@link Audience}s.
*
* @return the audiences
*/
default @NotNull Iterable<? extends Audience> getViewersAsAudiences() {
return this.getViewers();
}
}

View File

@ -1,5 +1,6 @@
package net.minestom.server.advancements;
import net.kyori.adventure.text.Component;
import net.minestom.server.chat.JsonMessage;
import net.minestom.server.entity.Player;
import net.minestom.server.item.ItemStack;
@ -23,8 +24,8 @@ public class Advancement {
private boolean achieved;
private JsonMessage title;
private JsonMessage description;
private Component title;
private Component description;
private ItemStack icon;
@ -42,9 +43,29 @@ public class Advancement {
// Packet
private AdvancementsPacket.Criteria criteria;
/**
* @deprecated Use {@link #Advancement(Component, Component, ItemStack, FrameType, float, float)}
*/
@Deprecated
public Advancement(@NotNull JsonMessage title, JsonMessage description,
@NotNull ItemStack icon, @NotNull FrameType frameType,
float x, float y) {
this(title.asComponent(), description.asComponent(), icon, frameType, x, y);
}
/**
* @deprecated Use {@link #Advancement(Component, Component, Material, FrameType, float, float)}
*/
@Deprecated
public Advancement(@NotNull JsonMessage title, @NotNull JsonMessage description,
@NotNull Material icon, @NotNull FrameType frameType,
float x, float y) {
this(title, description, ItemStack.of(icon), frameType, x, y);
}
public Advancement(@NotNull Component title, Component description,
@NotNull ItemStack icon, @NotNull FrameType frameType,
float x, float y) {
this.title = title;
this.description = description;
this.icon = icon;
@ -53,10 +74,10 @@ public class Advancement {
this.y = y;
}
public Advancement(@NotNull JsonMessage title, @NotNull JsonMessage description,
public Advancement(@NotNull Component title, @NotNull Component description,
@NotNull Material icon, @NotNull FrameType frameType,
float x, float y) {
this(title, description, new ItemStack(icon, (byte) 1), frameType, x, y);
this(title, description, ItemStack.of(icon), frameType, x, y);
}
/**
@ -94,14 +115,25 @@ public class Advancement {
this.tab = tab;
}
/**
* Gets the title of the advancement.
*
* @return the title
*/
public Component getTitle() {
return title;
}
/**
* Gets the title of the advancement.
*
* @return the advancement title
* @deprecated Use {@link #getTitle()}
*/
@NotNull
public JsonMessage getTitle() {
return title;
@Deprecated
public JsonMessage getTitleJson() {
return JsonMessage.fromComponent(title);
}
/**
@ -109,31 +141,67 @@ public class Advancement {
*
* @param title the new title
*/
public void setTitle(@NotNull JsonMessage title) {
public void setTitle(@NotNull Component title) {
this.title = title;
update();
}
/**
* Changes the advancement title.
*
* @param title the new title
* @deprecated Use {@link #setTitle(Component)}
*/
@Deprecated
public void setTitle(@NotNull JsonMessage title) {
this.title = title.asComponent();
update();
}
/**
* Gets the description of the advancement.
*
* @return the description title
*/
@NotNull
public JsonMessage getDescription() {
public Component getDescription() {
return description;
}
/**
* Gets the description of the advancement.
*
* @return the description title
* @deprecated Use {@link #getDescription()}
*/
@NotNull
@Deprecated
public JsonMessage getDescriptionJson() {
return JsonMessage.fromComponent(description);
}
/**
* Changes the description title.
*
* @param description the new description
*/
public void setDescription(@NotNull JsonMessage description) {
public void setDescription(@NotNull Component description) {
this.description = description;
update();
}
/**
* Changes the description title.
*
* @param description the new description
* @deprecated Use {@link #setDescription(Component)}
*/
@Deprecated
public void setDescription(@NotNull JsonMessage description) {
this.description = description.asComponent();
update();
}
/**
* Gets the advancement icon.
*

View File

@ -1,6 +1,6 @@
package net.minestom.server.advancements;
import net.minestom.server.chat.ColoredText;
import net.kyori.adventure.text.Component;
import net.minestom.server.chat.JsonMessage;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
@ -15,6 +15,10 @@ import org.jetbrains.annotations.Nullable;
*/
public class AdvancementRoot extends Advancement {
/**
* @deprecated Use {@link #AdvancementRoot(Component, Component, ItemStack, FrameType, float, float, String)}
*/
@Deprecated
public AdvancementRoot(@NotNull JsonMessage title, @NotNull JsonMessage description,
@NotNull ItemStack icon, @NotNull FrameType frameType,
float x, float y,
@ -23,6 +27,10 @@ public class AdvancementRoot extends Advancement {
setBackground(background);
}
/**
* @deprecated Use {@link #AdvancementRoot(Component, Component, Material, FrameType, float, float, String)}
*/
@Deprecated
public AdvancementRoot(@NotNull JsonMessage title, @NotNull JsonMessage description,
@NotNull Material icon, FrameType frameType,
float x, float y,
@ -31,4 +39,20 @@ public class AdvancementRoot extends Advancement {
setBackground(background);
}
public AdvancementRoot(@NotNull Component title, @NotNull Component description,
@NotNull ItemStack icon, @NotNull FrameType frameType,
float x, float y,
@Nullable String background) {
super(title, description, icon, frameType, x, y);
setBackground(background);
}
public AdvancementRoot(@NotNull Component title, @NotNull Component description,
@NotNull Material icon, FrameType frameType,
float x, float y,
@Nullable String background) {
super(title, description, icon, frameType, x, y);
setBackground(background);
}
}

View File

@ -1,5 +1,6 @@
package net.minestom.server.advancements.notifications;
import net.kyori.adventure.text.Component;
import net.minestom.server.advancements.FrameType;
import net.minestom.server.chat.JsonMessage;
import net.minestom.server.item.ItemStack;
@ -11,20 +12,46 @@ import org.jetbrains.annotations.NotNull;
*/
public class Notification {
private final JsonMessage title;
private final Component title;
private final FrameType frameType;
private final ItemStack icon;
/**
* @deprecated Use {@link #Notification(Component, FrameType, ItemStack)}
*/
@Deprecated
public Notification(@NotNull JsonMessage title, @NotNull FrameType frameType, @NotNull ItemStack icon) {
this(title.asComponent(), frameType, icon);
}
/**
* @deprecated Use {@link #Notification(Component, FrameType, Material)}
*/
@Deprecated
public Notification(@NotNull JsonMessage title, @NotNull FrameType frameType, @NotNull Material icon) {
this(title.asComponent(), frameType, icon);
}
public Notification(@NotNull Component title, @NotNull FrameType frameType, @NotNull Material icon) {
this(title, frameType, ItemStack.of(icon));
}
public Notification(@NotNull Component title, @NotNull FrameType frameType, @NotNull ItemStack icon) {
this.title = title;
this.frameType = frameType;
this.icon = icon;
}
public Notification(@NotNull JsonMessage title, @NotNull FrameType frameType, @NotNull Material icon) {
this.title = title;
this.frameType = frameType;
this.icon = new ItemStack(icon, (byte) 1);
/**
* Gets the title of the notification.
*
* @return the notification title
* @deprecated Use {@link #getTitle()}
*/
@NotNull
@Deprecated
public JsonMessage getTitleJson() {
return JsonMessage.fromComponent(title);
}
/**
@ -32,8 +59,7 @@ public class Notification {
*
* @return the notification title
*/
@NotNull
public JsonMessage getTitle() {
public Component getTitle() {
return title;
}

View File

@ -1,6 +1,6 @@
package net.minestom.server.advancements.notifications;
import net.minestom.server.chat.ColoredText;
import net.kyori.adventure.text.Component;
import net.minestom.server.entity.Player;
import net.minestom.server.network.packet.server.play.AdvancementsPacket;
import net.minestom.server.network.player.PlayerConnection;
@ -84,7 +84,7 @@ public class NotificationCenter {
{
displayData.title = notification.getTitle();
// Description is required, but never shown/seen so, small Easter egg.
displayData.description = ColoredText.of("Articdive was here. #Minestom");
displayData.description = Component.text("Articdive was here. #Minestom");
displayData.icon = notification.getIcon();
displayData.frameType = notification.getFrameType();
displayData.flags = 0x6;

View File

@ -0,0 +1,171 @@
package net.minestom.server.adventure;
import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import net.kyori.adventure.bossbar.BossBar;
import net.kyori.adventure.sound.Sound;
import net.kyori.adventure.sound.SoundStop;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minestom.server.entity.Entity;
import net.minestom.server.network.packet.server.ServerPacket;
import net.minestom.server.network.packet.server.play.EntitySoundEffectPacket;
import net.minestom.server.network.packet.server.play.NamedSoundEffectPacket;
import net.minestom.server.network.packet.server.play.SoundEffectPacket;
import net.minestom.server.network.packet.server.play.StopSoundPacket;
import net.minestom.server.registry.Registries;
import net.minestom.server.sound.SoundEvent;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
/**
* Utility methods to convert adventure enums to their packet values.
*/
public class AdventurePacketConvertor {
private static final Object2IntMap<NamedTextColor> NAMED_TEXT_COLOR_ID_MAP = new Object2IntArrayMap<>(16);
static {
NAMED_TEXT_COLOR_ID_MAP.put(NamedTextColor.BLACK, 0);
NAMED_TEXT_COLOR_ID_MAP.put(NamedTextColor.DARK_BLUE, 1);
NAMED_TEXT_COLOR_ID_MAP.put(NamedTextColor.DARK_GREEN, 2);
NAMED_TEXT_COLOR_ID_MAP.put(NamedTextColor.DARK_AQUA, 3);
NAMED_TEXT_COLOR_ID_MAP.put(NamedTextColor.DARK_RED, 4);
NAMED_TEXT_COLOR_ID_MAP.put(NamedTextColor.DARK_PURPLE, 5);
NAMED_TEXT_COLOR_ID_MAP.put(NamedTextColor.GOLD, 6);
NAMED_TEXT_COLOR_ID_MAP.put(NamedTextColor.GRAY, 7);
NAMED_TEXT_COLOR_ID_MAP.put(NamedTextColor.DARK_GRAY, 8);
NAMED_TEXT_COLOR_ID_MAP.put(NamedTextColor.BLUE, 9);
NAMED_TEXT_COLOR_ID_MAP.put(NamedTextColor.GREEN, 10);
NAMED_TEXT_COLOR_ID_MAP.put(NamedTextColor.AQUA, 11);
NAMED_TEXT_COLOR_ID_MAP.put(NamedTextColor.RED, 12);
NAMED_TEXT_COLOR_ID_MAP.put(NamedTextColor.LIGHT_PURPLE, 13);
NAMED_TEXT_COLOR_ID_MAP.put(NamedTextColor.YELLOW, 14);
NAMED_TEXT_COLOR_ID_MAP.put(NamedTextColor.WHITE, 15);
}
/**
* Gets the int value of a boss bar overlay.
* @param overlay the overlay
* @return the value
*/
public static int getBossBarOverlayValue(@NotNull BossBar.Overlay overlay) {
return overlay.ordinal();
}
/**
* Gets the byte value of a collection of boss bar flags.
* @param flags the flags
* @return the value
*/
public static byte getBossBarFlagValue(@NotNull Collection<BossBar.Flag> flags) {
byte val = 0x0;
for (BossBar.Flag flag : flags) {
val |= flag.ordinal();
}
return val;
}
/**
* Gets the int value of a boss bar color.
* @param color the color
* @return the value
*/
public static int getBossBarColorValue(@NotNull BossBar.Color color) {
return color.ordinal();
}
/**
* Gets the int value of a sound source.
* @param source the source
* @return the value
*/
public static int getSoundSourceValue(@NotNull Sound.Source source) {
return source.ordinal();
}
/**
* Gets the int value from a named text color.
* @param color the color
* @return the int value
*/
public static int getNamedTextColorValue(@NotNull NamedTextColor color) {
return NAMED_TEXT_COLOR_ID_MAP.getInt(color);
}
/**
* Creates a sound packet from a sound and a location.
* @param sound the sound
* @param x the x coordinate
* @param y the y coordinate
* @param z the z coordinate
* @return the sound packet
*/
public static ServerPacket createSoundPacket(@NotNull Sound sound, double x, double y, double z) {
SoundEvent minestomSound = Registries.getSoundEvent(sound.name());
if (minestomSound == null) {
NamedSoundEffectPacket packet = new NamedSoundEffectPacket();
packet.soundName = sound.name().asString();
packet.soundSource = sound.source();
packet.x = (int) x;
packet.y = (int) y;
packet.z = (int) z;
packet.volume = sound.volume();
packet.pitch = sound.pitch();
return packet;
} else {
SoundEffectPacket packet = new SoundEffectPacket();
packet.soundId = minestomSound.getId();
packet.soundSource = sound.source();
packet.x = (int) x;
packet.y = (int) y;
packet.z = (int) z;
packet.volume = sound.volume();
packet.pitch = sound.pitch();
return packet;
}
}
/**
* Creates an entity sound packet from an Adventure sound.
* @param sound the sound
* @param entity the entity the sound is coming from
* @return the packet
*/
public static ServerPacket createEntitySoundPacket(@NotNull Sound sound, @NotNull Entity entity) {
SoundEvent soundEvent = Registries.getSoundEvent(sound.name());
if (soundEvent == null) {
throw new IllegalArgumentException("Sound must be a valid sound event.");
} else {
EntitySoundEffectPacket packet = new EntitySoundEffectPacket();
packet.soundId = soundEvent.getId();
packet.soundSource = sound.source();
packet.entityId = entity.getEntityId();
packet.volume = sound.volume();
packet.pitch = sound.pitch();
return packet;
}
}
/**
* Creates a sound stop packet from a sound stop.
* @param stop the sound stop
* @return the sound stop packet
*/
public static ServerPacket createSoundStopPacket(@NotNull SoundStop stop) {
StopSoundPacket packet = new StopSoundPacket();
packet.flags = 0x0;
if (stop.source() != null) {
packet.flags |= 0x1;
packet.source = AdventurePacketConvertor.getSoundSourceValue(stop.source());
}
if (stop.sound() != null) {
packet.flags |= 0x2;
packet.sound = stop.sound().asString();
}
return packet;
}
}

View File

@ -0,0 +1,180 @@
package net.minestom.server.adventure;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TranslatableComponent;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.translation.GlobalTranslator;
import net.kyori.adventure.translation.TranslationRegistry;
import net.kyori.adventure.translation.Translator;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Locale;
import java.util.Objects;
import java.util.function.Function;
/**
* Manager class for handling Adventure serialization. By default AdventureSerializer will simply
* serialize components to Strings using {@link GsonComponentSerializer}. However, AdventureSerializer
* class can be used to change the way text is serialized. For example, a pre-JSON
* implementation of Minestom could change AdventureSerializer to the plain component serializer.
* <br><br>
* This manager also performs translation on all messages and the {@code serialize}
* method should be used when converting {@link Component}s into strings. This allows for
* messages with {@link TranslatableComponent} to be automatically translated into the locale
* of specific players, or other elements which implement {@link Localizable}. To add your
* own translations, use {@link GlobalTranslator#addSource(Translator)} with a
* {@link TranslationRegistry} or your own implementation of {@link Translator}.
*/
public class AdventureSerializer {
/**
* If components should be automatically translated in outgoing packets.
*/
public static final boolean AUTOMATIC_COMPONENT_TRANSLATION = false;
protected static final Localizable NULL_LOCALIZABLE = () -> null;
private static Function<Component, String> serializer = component -> GsonComponentSerializer.gson().serialize(component);
private static Locale defaultLocale = Locale.US;
private AdventureSerializer() {}
/**
* Gets the root serializer that is used to convert components into strings.
*
* @return the serializer
*/
public static @NotNull Function<Component, String> getSerializer() {
return AdventureSerializer.serializer;
}
/**
* Sets the root serializer that is used to convert components into strings.
*
* @param serializer the serializer
*/
public static void setSerializer(@NotNull Function<Component, String> serializer) {
AdventureSerializer.serializer = serializer;
}
/**
* Gets the default locale used to translate {@link TranslatableComponent} if, when
* {@link #translate(Component, Localizable)} is called with a localizable that
* does not have a locale.
*
* @return the default locale
*/
public static @NotNull Locale getDefaultLocale() {
return defaultLocale;
}
/**
* Sets the default locale used to translate {@link TranslatableComponent} if, when
* {@link #translate(Component, Localizable)} is called with a localizable that
* does not have a locale.
*
* @param defaultLocale the new default locale
*/
public static void setDefaultLocale(@NotNull Locale defaultLocale) {
AdventureSerializer.defaultLocale = defaultLocale;
}
/**
* Gets the global translator object used by AdventureSerializer manager. This is just shorthand for
* {@link GlobalTranslator#get()}.
*
* @return the global translator
*/
public static @NotNull GlobalTranslator getTranslator() {
return GlobalTranslator.get();
}
/**
* Prepares a component for serialization. This runs the component through the
* translator for the localizable's locale.
*
* @param component the component
* @param localizable the localizable
*
* @return the prepared component
*/
public static @NotNull Component translate(@NotNull Component component, @NotNull Localizable localizable) {
return GlobalTranslator.renderer().render(component, Objects.requireNonNullElse(localizable.getLocale(), AdventureSerializer.getDefaultLocale()));
}
/**
* Prepares a component for serialization. This runs the component through the
* translator for the locale.
*
* @param component the component
* @param locale the locale
*
* @return the prepared component
*/
public static @NotNull Component translate(@NotNull Component component, @NotNull Locale locale) {
return GlobalTranslator.renderer().render(component, locale);
}
/**
* Serializes a component into a string using {@link #getSerializer()}.
*
* @param component the component
*
* @return the serialized string
*/
public static @NotNull String serialize(@NotNull Component component) {
return AdventureSerializer.serializer.apply(component);
}
/**
* Prepares and then serializes a component.
*
* @param component the component
* @param localizable the localisable
*
* @return the string
*/
public static String translateAndSerialize(@NotNull Component component, @NotNull Localizable localizable) {
return AdventureSerializer.translateAndSerialize(component, Objects.requireNonNullElse(localizable.getLocale(), AdventureSerializer.getDefaultLocale()));
}
/**
* Prepares and then serializes a component.
*
* @param component the component
* @param locale the locale
*
* @return the string
*/
public static String translateAndSerialize(@NotNull Component component, @NotNull Locale locale) {
return AdventureSerializer.serialize(AdventureSerializer.translate(component, locale));
}
/**
* Checks if a component can be translated server-side. This is done by running the
* component through the translator and seeing if the translated component is equal
* to the non translated component.
* @param component the component
* @return {@code true} if the component can be translated server-side,
* {@code false} otherwise
*/
public static boolean isTranslatable(@NotNull Component component) {
return !component.equals(AdventureSerializer.translate(component, AdventureSerializer.getDefaultLocale()));
}
/**
* Checks if any of a series of components are translatable server-side.
* @param components the components
* @return {@code true} if any of the components can be translated server-side,
* {@code false} otherwise
*/
public static boolean areAnyTranslatable(@NotNull Collection<Component> components) {
for (Component component : components) {
if (AdventureSerializer.isTranslatable(component)) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,43 @@
package net.minestom.server.adventure;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
/**
* Represents an object that holds some amount of components.
*
* @param <T> the holding class
*/
public interface ComponentHolder<T> {
/**
* Gets the components held by this object.
*
* @return the components
*/
@NotNull Collection<Component> components();
/**
* Returns a copy of this object. For each component this object holds, the operator
* is applied to the copy before returning.
*
* @param operator the operator
* @return the copy
*/
@NotNull T copyWithOperator(@NotNull UnaryOperator<Component> operator);
/**
* Visits each component held by this object.
*
* @param visitor the visitor
*/
default void visitComponents(@NotNull Consumer<Component> visitor) {
for (Component component : this.components()) {
visitor.accept(component);
}
}
}

View File

@ -0,0 +1,37 @@
package net.minestom.server.adventure;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Locale;
/**
* Represents something which can have a locale.
*/
public interface Localizable {
/**
* Gets a localizable that returns {@code null} for all calls to {@link #getLocale()}.
*
* @return the empty localizable
*/
static @NotNull Localizable empty() {
return AdventureSerializer.NULL_LOCALIZABLE;
}
/**
* Gets the locale.
*
* @return the locale, or {@code null} if they do not have a locale set
*/
@Nullable Locale getLocale();
/**
* Sets the locale. This can be set to {@code null} to remove a locale registration.
*
* @param locale the new locale
*/
default void setLocale(@Nullable Locale locale) {
throw new UnsupportedOperationException("You cannot set the locale for this object!");
}
}

View File

@ -0,0 +1,125 @@
package net.minestom.server.adventure.audience;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.key.Keyed;
import net.minestom.server.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.function.Predicate;
/**
* A generic provider of {@link Audience audiences} or some subtype.
*
* @param <A> the type that is provided
*/
public interface AudienceProvider<A> {
/**
* Gets all audience members. This returns {@link #players()} combined with
* {@link #customs()} and {@link #console()}. This can be a costly operation, so it
* is often preferable to use {@link #server()} instead.
*
* @return all audience members
*/
@NotNull A all();
/**
* Gets all audience members that are of type {@link Player}.
*
* @return all players
*/
@NotNull A players();
/**
* Gets all audience members that are of type {@link Player} and match the predicate.
*
* @param filter the predicate
* @return all players matching the predicate
*/
@NotNull A players(@NotNull Predicate<Player> filter);
/**
* Gets the console as an audience.
*
* @return the console
*/
@NotNull A console();
/**
* Gets the combination of {@link #players()} and {@link #console()}.
*
* @return the audience of all players and the console
*/
@NotNull A server();
/**
* Gets all custom audience members stored using the given keyed object.
*
* @param keyed the keyed object
* @return all custom audience members stored using the key of the object
*/
default @NotNull A custom(@NotNull Keyed keyed) {
return this.custom(keyed.key());
}
/**
* Gets all custom audience members stored using the given key.
*
* @param key the key
* @return all custom audience members stored using the key
*/
@NotNull A custom(@NotNull Key key);
/**
* Gets all custom audience members stored using the given keyed object that match
* the given predicate.
*
* @param keyed the keyed object
* @param filter the predicate
* @return all custom audience members stored using the key
*/
default @NotNull A custom(@NotNull Keyed keyed, Predicate<Audience> filter) {
return this.custom(keyed.key(), filter);
}
/**
* Gets all custom audience members stored using the given key that match the
* given predicate.
*
* @param key the key
* @param filter the predicate
* @return all custom audience members stored using the key
*/
@NotNull A custom(@NotNull Key key, Predicate<Audience> filter);
/**
* Gets all custom audience members.
*
* @return all custom audience members
*/
@NotNull A customs();
/**
* Gets all custom audience members matching the given predicate.
*
* @param filter the predicate
* @return all matching custom audience members
*/
@NotNull A customs(@NotNull Predicate<Audience> filter);
/**
* Gets all audience members that match the given predicate.
*
* @param filter the predicate
* @return all matching audience members
*/
@NotNull A all(@NotNull Predicate<Audience> filter);
/**
* Gets the audience registry used to register custom audiences.
*
* @return the registry
*/
@NotNull AudienceRegistry registry();
}

View File

@ -0,0 +1,133 @@
package net.minestom.server.adventure.audience;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.key.Keyed;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
* Holder of custom audiences.
*/
public class AudienceRegistry {
private final Map<Key, Collection<Audience>> registry;
private final Function<Key, Collection<Audience>> provider;
/**
* Creates a new audience registrar with a given backing map.
*
* @param backingMap the backing map
* @param backingCollection a provider for the backing collection
*/
public AudienceRegistry(@NotNull Map<Key, Collection<Audience>> backingMap, @NotNull Supplier<Collection<Audience>> backingCollection) {
this.registry = backingMap;
this.provider = key -> backingCollection.get();
}
/**
* Checks if this registry is empty.
*
* @return {@code true} if it is, {@code false} otherwise
*/
public boolean isEmpty() {
return this.registry.isEmpty();
}
/**
* Adds some audiences to the registry.
*
* @param keyed the provider of the key
* @param audiences the audiences
*/
public void register(@NotNull Keyed keyed, @NotNull Audience... audiences) {
this.register(keyed.key(), audiences);
}
/**
* Adds some audiences to the registry.
*
* @param keyed the provider of the key
* @param audiences the audiences
*/
public void register(@NotNull Keyed keyed, @NotNull Collection<Audience> audiences) {
this.register(keyed.key(), audiences);
}
/**
* Adds some audiences to the registry.
*
* @param key the key to store the audiences under
* @param audiences the audiences
*/
public void register(@NotNull Key key, @NotNull Audience... audiences) {
if (audiences == null || audiences.length == 0) {
return;
}
this.register(key, Arrays.asList(audiences));
}
/**
* Adds some audiences to the registry.
*
* @param key the key to store the audiences under
* @param audiences the audiences
*/
public void register(@NotNull Key key, @NotNull Collection<Audience> audiences) {
if (!audiences.isEmpty()) {
this.registry.computeIfAbsent(key, this.provider).addAll(audiences);
}
}
/**
* Gets every audience in the registry.
*
* @return an iterable containing every audience member
*/
public @NotNull Iterable<? extends Audience> all() {
if (this.isEmpty()) {
return Collections.emptyList();
} else {
return this.registry.values().stream().flatMap(Collection::stream).collect(Collectors.toUnmodifiableList());
}
}
/**
* Gets every audience in the registry under a specific key.
*
* @param keyed the key provider
* @return an iterable containing the audience members
*/
public @NotNull Iterable<? extends Audience> of(@NotNull Keyed keyed) {
return this.of(keyed.key());
}
/**
* Gets every audience in the registry under a specific key.
*
* @param key the key
* @return an iterable containing the audience members
*/
public @NotNull Iterable<? extends Audience> of(@NotNull Key key) {
return Collections.unmodifiableCollection(this.registry.getOrDefault(key, this.provider.apply(null)));
}
/**
* Gets every audience member in the registry who matches a given predicate.
*
* @param filter the predicate
* @return the matching audience members
*/
public @NotNull Iterable<? extends Audience> of(@NotNull Predicate<Audience> filter) {
return this.registry.values().stream().flatMap(Collection::stream).filter(filter).collect(Collectors.toUnmodifiableList());
}
}

View File

@ -0,0 +1,166 @@
package net.minestom.server.adventure.audience;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.key.Keyed;
import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* Utility class to access Adventure audiences.
*/
public class Audiences {
private static final SingleAudienceProvider audience = new SingleAudienceProvider();
/**
* Gets the {@link AudienceProvider} that provides forwarding audiences.
*
* @return the instance
*/
public static @NotNull AudienceProvider<Audience> single() {
return audience;
}
/**
* Gets the {@link AudienceProvider} that provides iterables of audience members.
*
* @return the instance
*/
public static @NotNull AudienceProvider<Iterable<? extends Audience>> iterable() {
return audience.collection;
}
/**
* Gets all audience members. This returns {@link #players()} combined with
* {@link #customs()} and {@link #console()}. This can be a costly operation, so it
* is often preferable to use {@link #server()} instead.
*
* @return all audience members
*/
public static @NotNull Audience all() {
return Audience.audience(audience.server, audience.customs());
}
/**
* Gets all audience members that are of type {@link Player}.
*
* @return all players
*/
public static @NotNull Audience players() {
return audience.players;
}
/**
* Gets all audience members that are of type {@link Player} and match the predicate.
*
* @param filter the predicate
* @return all players matching the predicate
*/
public static @NotNull Audience players(@NotNull Predicate<Player> filter) {
return PacketGroupingAudience.of(MinecraftServer.getConnectionManager().getOnlinePlayers().stream().filter(filter).collect(Collectors.toList()));
}
/**
* Gets the console as an audience.
*
* @return the console
*/
public static @NotNull Audience console() {
return MinecraftServer.getCommandManager().getConsoleSender();
}
/**
* Gets the combination of {@link #players()} and {@link #console()}.
*
* @return the audience of all players and the console
*/
public static @NotNull Audience server() {
return audience.server;
}
/**
* Gets all custom audience members.
*
* @return all custom audience members
*/
public static @NotNull Audience customs() {
return Audience.audience(audience.iterable().customs());
}
/**
* Gets all custom audience members stored using the given keyed object.
*
* @param keyed the keyed object
* @return all custom audience members stored using the key of the object
*/
public static @NotNull Audience custom(@NotNull Keyed keyed) {
return custom(keyed.key());
}
/**
* Gets all custom audience members stored using the given key.
*
* @param key the key
* @return all custom audience members stored using the key
*/
public static @NotNull Audience custom(@NotNull Key key) {
return Audience.audience(audience.iterable().custom(key));
}
/**
* Gets all custom audience members stored using the given keyed object that match
* the given predicate.
*
* @param keyed the keyed object
* @param filter the predicate
* @return all custom audience members stored using the key
*/
public static @NotNull Audience custom(@NotNull Keyed keyed, Predicate<Audience> filter) {
return custom(keyed.key(), filter);
}
/**
* Gets all custom audience members stored using the given key that match the
* given predicate.
*
* @param key the key
* @param filter the predicate
* @return all custom audience members stored using the key
*/
public static @NotNull Audience custom(@NotNull Key key, Predicate<Audience> filter) {
return Audience.audience(audience.iterable().custom(key, filter));
}
/**
* Gets all custom audience members matching the given predicate.
*
* @param filter the predicate
* @return all matching custom audience members
*/
public static @NotNull Audience customs(@NotNull Predicate<Audience> filter) {
return Audience.audience(audience.iterable().customs(filter));
}
/**
* Gets all audience members that match the given predicate.
*
* @param filter the predicate
* @return all matching audience members
*/
public static @NotNull Audience all(@NotNull Predicate<Audience> filter) {
return Audience.audience(audience.iterable().all(filter));
}
/**
* Gets the audience registry used to register custom audiences.
*
* @return the registry
*/
public static @NotNull AudienceRegistry registry() {
return audience.iterable().registry();
}
}

View File

@ -0,0 +1,84 @@
package net.minestom.server.adventure.audience;
import com.google.common.collect.Iterables;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.key.Key;
import net.minestom.server.MinecraftServer;
import net.minestom.server.command.ConsoleSender;
import net.minestom.server.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
/**
* A provider of iterable audiences.
*/
class IterableAudienceProvider implements AudienceProvider<Iterable<? extends Audience>> {
private final Collection<ConsoleSender> console = Collections.singleton(MinecraftServer.getCommandManager().getConsoleSender());
private final AudienceRegistry registry = new AudienceRegistry(new ConcurrentHashMap<>(), CopyOnWriteArrayList::new);
protected IterableAudienceProvider() {
}
@Override
public @NotNull Iterable<? extends Audience> all() {
return Iterables.concat(this.players(), this.console(), this.customs());
}
@Override
public @NotNull Iterable<? extends Audience> players() {
return MinecraftServer.getConnectionManager().getOnlinePlayers();
}
@Override
public @NotNull Iterable<? extends Audience> players(@NotNull Predicate<Player> filter) {
return MinecraftServer.getConnectionManager().getOnlinePlayers().stream().filter(filter).collect(Collectors.toList());
}
@Override
public @NotNull Iterable<? extends Audience> console() {
return this.console;
}
@Override
public @NotNull Iterable<? extends Audience> server() {
return Iterables.concat(this.players(), this.console());
}
@Override
public @NotNull Iterable<? extends Audience> customs() {
return this.registry.all();
}
@Override
public @NotNull Iterable<? extends Audience> custom(@NotNull Key key) {
return this.registry.of(key);
}
@Override
public @NotNull Iterable<? extends Audience> custom(@NotNull Key key, Predicate<Audience> filter) {
return StreamSupport.stream(this.registry.of(key).spliterator(), false).filter(filter).collect(Collectors.toList());
}
@Override
public @NotNull Iterable<? extends Audience> customs(@NotNull Predicate<Audience> filter) {
return this.registry.of(filter);
}
@Override
public @NotNull Iterable<? extends Audience> all(@NotNull Predicate<Audience> filter) {
return StreamSupport.stream(this.all().spliterator(), false).filter(filter).collect(Collectors.toList());
}
@Override
public @NotNull AudienceRegistry registry() {
return this.registry;
}
}

View File

@ -0,0 +1,102 @@
package net.minestom.server.adventure.audience;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.audience.ForwardingAudience;
import net.kyori.adventure.audience.MessageType;
import net.kyori.adventure.bossbar.BossBar;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.sound.Sound;
import net.kyori.adventure.sound.SoundStop;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.title.Title;
import net.minestom.server.MinecraftServer;
import net.minestom.server.adventure.AdventurePacketConvertor;
import net.minestom.server.entity.Player;
import net.minestom.server.network.packet.server.play.ChatMessagePacket;
import net.minestom.server.network.packet.server.play.PlayerListHeaderAndFooterPacket;
import net.minestom.server.network.packet.server.play.TitlePacket;
import net.minestom.server.utils.PacketUtils;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
/**
* An audience implementation that sends grouped packets if possible.
*/
public interface PacketGroupingAudience extends ForwardingAudience {
/**
* Creates a packet grouping audience that copies an iterable of players. The
* underlying collection is not copied, so changes to the collection will be
* reflected in the audience.
*
* @param players the players
* @return the audience
*/
static PacketGroupingAudience of(Collection<Player> players) {
return () -> players;
}
/**
* Gets an iterable of the players this audience contains.
*
* @return the connections
*/
@NotNull Collection<Player> getPlayers();
@Override
default void sendMessage(@NotNull Identity source, @NotNull Component message, @NotNull MessageType type) {
PacketUtils.sendGroupedPacket(this.getPlayers(), new ChatMessagePacket(message, ChatMessagePacket.Position.fromMessageType(type), source.uuid()));
}
@Override
default void sendActionBar(@NotNull Component message) {
PacketUtils.sendGroupedPacket(this.getPlayers(), new TitlePacket(TitlePacket.Action.SET_ACTION_BAR, message));
}
@Override
default void sendPlayerListHeaderAndFooter(@NotNull Component header, @NotNull Component footer) {
PacketUtils.sendGroupedPacket(this.getPlayers(), new PlayerListHeaderAndFooterPacket(header, footer));
}
@Override
default void showTitle(@NotNull Title title) {
PacketUtils.sendGroupedPacket(this.getPlayers(), new TitlePacket(TitlePacket.Action.SET_TITLE, title.title()));
PacketUtils.sendGroupedPacket(this.getPlayers(), new TitlePacket(TitlePacket.Action.SET_SUBTITLE, title.subtitle()));
}
@Override
default void clearTitle() {
PacketUtils.sendGroupedPacket(this.getPlayers(), new TitlePacket(TitlePacket.Action.HIDE));
}
@Override
default void resetTitle() {
PacketUtils.sendGroupedPacket(this.getPlayers(), new TitlePacket(TitlePacket.Action.RESET));
}
@Override
default void showBossBar(@NotNull BossBar bar) {
MinecraftServer.getBossBarManager().addBossBar(this.getPlayers(), bar);
}
@Override
default void hideBossBar(@NotNull BossBar bar) {
MinecraftServer.getBossBarManager().removeBossBar(this.getPlayers(), bar);
}
@Override
default void playSound(@NotNull Sound sound, double x, double y, double z) {
PacketUtils.sendGroupedPacket(this.getPlayers(), AdventurePacketConvertor.createSoundPacket(sound, x, y, z));
}
@Override
default void stopSound(@NotNull SoundStop stop) {
PacketUtils.sendGroupedPacket(this.getPlayers(), AdventurePacketConvertor.createSoundStopPacket(stop));
}
@Override
default @NotNull Iterable<? extends Audience> audiences() {
return this.getPlayers();
}
}

View File

@ -0,0 +1,88 @@
package net.minestom.server.adventure.audience;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.key.Key;
import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* A provider of audiences. For complex returns, this instance is backed by
* {@link IterableAudienceProvider}.
*/
class SingleAudienceProvider implements AudienceProvider<Audience> {
protected final IterableAudienceProvider collection = new IterableAudienceProvider();
protected final Audience players = PacketGroupingAudience.of(MinecraftServer.getConnectionManager().getOnlinePlayers());
protected final Audience server = Audience.audience(this.players, MinecraftServer.getCommandManager().getConsoleSender());
protected SingleAudienceProvider() {
}
/**
* Gets the {@link IterableAudienceProvider} instance.
*
* @return the instance
*/
public @NotNull IterableAudienceProvider iterable() {
return this.collection;
}
@Override
public @NotNull Audience all() {
return Audience.audience(this.server, this.customs());
}
@Override
public @NotNull Audience players() {
return this.players;
}
@Override
public @NotNull Audience players(@NotNull Predicate<Player> filter) {
return PacketGroupingAudience.of(MinecraftServer.getConnectionManager().getOnlinePlayers().stream().filter(filter).collect(Collectors.toList()));
}
@Override
public @NotNull Audience console() {
return MinecraftServer.getCommandManager().getConsoleSender();
}
@Override
public @NotNull Audience server() {
return this.server;
}
@Override
public @NotNull Audience customs() {
return Audience.audience(this.iterable().customs());
}
@Override
public @NotNull Audience custom(@NotNull Key key) {
return Audience.audience(this.iterable().custom(key));
}
@Override
public @NotNull Audience custom(@NotNull Key key, Predicate<Audience> filter) {
return Audience.audience(this.iterable().custom(key, filter));
}
@Override
public @NotNull Audience customs(@NotNull Predicate<Audience> filter) {
return Audience.audience(this.iterable().customs(filter));
}
@Override
public @NotNull Audience all(@NotNull Predicate<Audience> filter) {
return Audience.audience(this.iterable().all(filter));
}
@Override
public @NotNull AudienceRegistry registry() {
return this.iterable().registry();
}
}

View File

@ -0,0 +1,99 @@
package net.minestom.server.adventure.bossbar;
import net.kyori.adventure.bossbar.BossBar;
import net.kyori.adventure.text.Component;
import net.minestom.server.Viewable;
import net.minestom.server.adventure.AdventurePacketConvertor;
import net.minestom.server.entity.Player;
import net.minestom.server.network.packet.server.play.BossBarPacket;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.function.Consumer;
import static net.minestom.server.network.packet.server.play.BossBarPacket.Action.*;
/**
* A holder of a boss bar. This class is not intended for public use, instead you should
* use {@link BossBarManager} to manage boss bars for players.
*/
final class BossBarHolder implements Viewable {
protected final UUID uuid = UUID.randomUUID();
protected final Set<Player> players = new CopyOnWriteArraySet<>();
protected final BossBar bar;
BossBarHolder(@NotNull BossBar bar) {
this.bar = bar;
}
@NotNull BossBarPacket createRemovePacket() {
return this.createGenericPacket(REMOVE, packet -> { });
}
@NotNull BossBarPacket createAddPacket() {
return this.createGenericPacket(ADD, packet -> {
packet.title = bar.name();
packet.color = bar.color();
packet.overlay = bar.overlay();
packet.health = bar.progress();
packet.flags = AdventurePacketConvertor.getBossBarFlagValue(bar.flags());
});
}
@NotNull BossBarPacket createPercentUpdate(float newPercent) {
return this.createGenericPacket(UPDATE_HEALTH, packet -> packet.health = newPercent);
}
@NotNull BossBarPacket createColorUpdate(@NotNull BossBar.Color color) {
return this.createGenericPacket(UPDATE_STYLE, packet -> {
packet.color = color;
packet.overlay = bar.overlay();
});
}
@NotNull BossBarPacket createTitleUpdate(@NotNull Component title) {
return this.createGenericPacket(UPDATE_TITLE, packet -> packet.title = title);
}
@NotNull BossBarPacket createFlagsUpdate() {
return createFlagsUpdate(bar.flags());
}
@NotNull BossBarPacket createFlagsUpdate(@NotNull Set<BossBar.Flag> newFlags) {
return this.createGenericPacket(UPDATE_FLAGS, packet -> packet.flags = AdventurePacketConvertor.getBossBarFlagValue(newFlags));
}
@NotNull BossBarPacket createOverlayUpdate(@NotNull BossBar.Overlay overlay) {
return this.createGenericPacket(UPDATE_STYLE, packet -> {
packet.overlay = overlay;
packet.color = bar.color();
});
}
private @NotNull BossBarPacket createGenericPacket(@NotNull BossBarPacket.Action action, @NotNull Consumer<BossBarPacket> consumer) {
BossBarPacket packet = new BossBarPacket();
packet.uuid = this.uuid;
packet.action = action;
consumer.accept(packet);
return packet;
}
@Override
public boolean addViewer(@NotNull Player player) {
return this.players.add(player);
}
@Override
public boolean removeViewer(@NotNull Player player) {
return this.players.remove(player);
}
@Override
public @NotNull Set<Player> getViewers() {
return Collections.unmodifiableSet(this.players);
}
}

View File

@ -0,0 +1,63 @@
package net.minestom.server.adventure.bossbar;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.bossbar.BossBar;
import net.kyori.adventure.text.Component;
import net.minestom.server.utils.PacketUtils;
import org.jetbrains.annotations.NotNull;
import java.util.Set;
import java.util.function.Consumer;
/**
* A listener for boss bar updates. This class is not intended for public use and it is
* automatically added to boss bars shown to players using the methods in
* {@link Audience}, instead you should use {@link BossBarManager} to manage boss bars
* for players.
*/
class BossBarListener implements BossBar.Listener {
private final BossBarManager manager;
/**
* Creates a new boss bar listener.
*
* @param manager the manager instance
*/
BossBarListener(BossBarManager manager) {
this.manager = manager;
}
@Override
public void bossBarNameChanged(@NotNull BossBar bar, @NotNull Component oldName, @NotNull Component newName) {
this.doIfRegistered(bar, holder -> PacketUtils.sendGroupedPacket(holder.players, holder.createTitleUpdate(newName)));
}
@Override
public void bossBarProgressChanged(@NotNull BossBar bar, float oldProgress, float newProgress) {
this.doIfRegistered(bar, holder -> PacketUtils.sendGroupedPacket(holder.players, holder.createPercentUpdate(newProgress)));
}
@Override
public void bossBarColorChanged(@NotNull BossBar bar, @NotNull BossBar.Color oldColor, @NotNull BossBar.Color newColor) {
this.doIfRegistered(bar, holder -> PacketUtils.sendGroupedPacket(holder.players, holder.createColorUpdate(newColor)));
}
@Override
public void bossBarOverlayChanged(@NotNull BossBar bar, BossBar.@NotNull Overlay oldOverlay, BossBar.@NotNull Overlay newOverlay) {
this.doIfRegistered(bar, holder -> PacketUtils.sendGroupedPacket(holder.players, holder.createOverlayUpdate(newOverlay)));
}
@Override
public void bossBarFlagsChanged(@NotNull BossBar bar, @NotNull Set<BossBar.Flag> flagsAdded, @NotNull Set<BossBar.Flag> flagsRemoved) {
this.doIfRegistered(bar, holder -> PacketUtils.sendGroupedPacket(holder.players, holder.createFlagsUpdate()));
}
private void doIfRegistered(@NotNull BossBar bar, @NotNull Consumer<BossBarHolder> consumer) {
BossBarHolder holder = this.manager.bars.get(bar);
if (holder != null) {
consumer.accept(holder);
}
}
}

View File

@ -0,0 +1,195 @@
package net.minestom.server.adventure.bossbar;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.bossbar.BossBar;
import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.Player;
import net.minestom.server.utils.PacketUtils;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* Manages all boss bars known to this Minestom instance. Although this class can be used
* to show boss bars to players, it is preferable to use the boss bar methods in the
* {@link Audience} class instead.
*
* <p>This implementation is heavily based on
* <a href="https://github.com/VelocityPowered/Velocity">Velocity</a>'s boss bar
* management system.</p>
*
* @see Audience#showBossBar(BossBar)
* @see Audience#hideBossBar(BossBar)
*/
public class BossBarManager {
private final BossBarListener listener = new BossBarListener(this);
private final Map<UUID, Set<BossBarHolder>> playerBars = new ConcurrentHashMap<>();
final Map<BossBar, BossBarHolder> bars = new ConcurrentHashMap<>();
/**
* Creates a new boss bar manager.
*
* @see MinecraftServer#getBossBarManager()
*/
public BossBarManager() {
}
/**
* Adds the specified player to the boss bar's viewers and spawns the boss bar, registering the
* boss bar if needed.
*
* @param player the intended viewer
* @param bar the boss bar to show
*/
public void addBossBar(@NotNull Player player, @NotNull BossBar bar) {
BossBarHolder holder = this.getOrCreateHandler(bar);
if (holder.addViewer(player)) {
player.getPlayerConnection().sendPacket(holder.createAddPacket());
this.playerBars.computeIfAbsent(player.getUuid(), uuid -> new HashSet<>()).add(holder);
}
}
/**
* Removes the specified player from the boss bar's viewers and despawns the boss bar.
*
* @param player the intended viewer
* @param bar the boss bar to hide
*/
public void removeBossBar(@NotNull Player player, @NotNull BossBar bar) {
BossBarHolder holder = this.bars.get(bar);
if (holder != null && holder.removeViewer(player)) {
player.getPlayerConnection().sendPacket(holder.createRemovePacket());
this.removePlayer(player, holder);
}
}
/**
* Adds the specified players to the boss bar's viewers and spawns the boss bar, registering the
* boss bar if needed.
*
* @param players the players
* @param bar the boss bar
*/
public void addBossBar(@NotNull Collection<Player> players, @NotNull BossBar bar) {
BossBarHolder holder = this.getOrCreateHandler(bar);
Collection<Player> addedPlayers = players.stream().filter(holder::addViewer).collect(Collectors.toList());
if (!addedPlayers.isEmpty()) {
PacketUtils.sendGroupedPacket(addedPlayers, holder.createAddPacket());
}
}
/**
* Removes the specified players from the boss bar's viewers and despawns the boss bar.
*
* @param players the intended viewers
* @param bar the boss bar to hide
*/
public void removeBossBar(@NotNull Collection<Player> players, @NotNull BossBar bar) {
BossBarHolder holder = this.bars.get(bar);
if (holder != null) {
Collection<Player> removedPlayers = players.stream().filter(holder::removeViewer).collect(Collectors.toList());
if (!removedPlayers.isEmpty()) {
PacketUtils.sendGroupedPacket(removedPlayers, holder.createRemovePacket());
}
}
}
/**
* Completely destroys a boss bar, removing it from all players.
*
* @param bossBar the boss bar
*/
public void destroyBossBar(@NotNull BossBar bossBar) {
BossBarHolder holder = this.bars.remove(bossBar);
if (holder != null) {
PacketUtils.sendGroupedPacket(holder.players, holder.createRemovePacket());
for (Player player : holder.players) {
this.removePlayer(player, holder);
}
}
}
/**
* Removes a player from all of their boss bars. Note that this method does not
* send any removal packets to the player. It is meant to be used when a player is
* disconnecting from the server.
*
* @param player the player
*/
public void removeAllBossBars(@NotNull Player player) {
Set<BossBarHolder> holders = this.playerBars.remove(player.getUuid());
if (holders != null) {
for (BossBarHolder holder : holders) {
holder.removeViewer(player);
}
}
}
/**
* Gets a collection of all boss bars currently visible to a given player.
*
* @param player the player
* @return the boss bars
*/
public @NotNull Collection<BossBar> getPlayerBossBars(@NotNull Player player) {
Collection<BossBarHolder> holders = this.playerBars.get(player.getUuid());
if (holders == null) {
return Collections.emptyList();
} else {
return holders.stream().map(holder -> holder.bar).collect(Collectors.toUnmodifiableList());
}
}
/**
* Gets all the players for whom the given boss bar is currently visible.
*
* @param bossBar the boss bar
* @return the players
*/
public @NotNull Collection<Player> getBossBarViewers(@NotNull BossBar bossBar) {
BossBarHolder holder = this.bars.get(bossBar);
if (holder == null) {
return Collections.emptyList();
} else {
return Collections.unmodifiableCollection(holder.players);
}
}
/**
* Gets or creates a handler for this bar.
*
* @param bar the bar
* @return the handler
*/
private @NotNull BossBarHolder getOrCreateHandler(@NotNull BossBar bar) {
return this.bars.computeIfAbsent(bar, key -> {
BossBarHolder holder = new BossBarHolder(key);
bar.addListener(this.listener);
return holder;
});
}
private void removePlayer(Player player, BossBarHolder holder) {
Set<BossBarHolder> holders = this.playerBars.get(player.getUuid());
if (holders != null) {
holders.remove(holder);
if (holders.isEmpty()) {
this.playerBars.remove(player.getUuid());
}
}
}
}

View File

@ -2,7 +2,9 @@ package net.minestom.server.bossbar;
/**
* Represents the displayed color of a {@link BossBar}.
* @deprecated Use {@link net.kyori.adventure.bossbar.BossBar.Color}
*/
@Deprecated
public enum BarColor {
PINK,
BLUE,
@ -10,5 +12,9 @@ public enum BarColor {
GREEN,
YELLOW,
PURPLE,
WHITE
WHITE;
public net.kyori.adventure.bossbar.BossBar.Color asAdventureColor() {
return net.kyori.adventure.bossbar.BossBar.Color.valueOf(this.name());
}
}

View File

@ -2,11 +2,18 @@ package net.minestom.server.bossbar;
/**
* Used to define the number of segments on a {@link BossBar}.
*
* @deprecated Use {@link net.kyori.adventure.bossbar.BossBar.Overlay}
*/
@Deprecated
public enum BarDivision {
SOLID,
SEGMENT_6,
SEGMENT_10,
SEGMENT_12,
SEGMENT_20
SEGMENT_20;
public net.kyori.adventure.bossbar.BossBar.Overlay asAdventureOverlay() {
return net.kyori.adventure.bossbar.BossBar.Overlay.values()[this.ordinal()];
}
}

View File

@ -19,7 +19,10 @@ import java.util.concurrent.CopyOnWriteArraySet;
* and add the {@link Player} you want using {@link #addViewer(Player)} and remove them using {@link #removeViewer(Player)}.
* <p>
* You can retrieve all the boss bars of a {@link Player} with {@link #getBossBars(Player)}.
*
* @deprecated Use {@link net.kyori.adventure.bossbar.BossBar}
*/
@Deprecated
public class BossBar implements Viewable {
private static final int MAX_BOSSBAR = 7;
@ -249,10 +252,10 @@ public class BossBar implements Viewable {
BossBarPacket bossBarPacket = new BossBarPacket();
bossBarPacket.uuid = uuid;
bossBarPacket.action = BossBarPacket.Action.ADD;
bossBarPacket.title = title;
bossBarPacket.title = title.asComponent();
bossBarPacket.health = progress;
bossBarPacket.color = color;
bossBarPacket.division = division;
bossBarPacket.color = color.asAdventureColor();
bossBarPacket.overlay = division.asAdventureOverlay();
bossBarPacket.flags = flags;
player.getPlayerConnection().sendPacket(bossBarPacket);
}
@ -275,7 +278,7 @@ public class BossBar implements Viewable {
BossBarPacket bossBarPacket = new BossBarPacket();
bossBarPacket.uuid = uuid;
bossBarPacket.action = BossBarPacket.Action.UPDATE_TITLE;
bossBarPacket.title = title;
bossBarPacket.title = title.asComponent();
sendPacketToViewers(bossBarPacket);
}
@ -291,7 +294,7 @@ public class BossBar implements Viewable {
BossBarPacket bossBarPacket = new BossBarPacket();
bossBarPacket.uuid = uuid;
bossBarPacket.action = BossBarPacket.Action.UPDATE_STYLE;
bossBarPacket.color = color;
bossBarPacket.color = color.asAdventureColor();
sendPacketToViewers(bossBarPacket);
}
}

View File

@ -1,10 +1,13 @@
package net.minestom.server.chat;
import net.kyori.adventure.text.event.ClickEvent;
import org.jetbrains.annotations.NotNull;
/**
* Represents a click event for a specific portion of the message.
* @deprecated Use {@link ClickEvent}
*/
@Deprecated
public class ChatClickEvent {
private final String action;

View File

@ -4,21 +4,29 @@ import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.kyori.adventure.text.format.*;
import net.minestom.server.color.Color;
import net.minestom.server.color.DyeColor;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* Represents a color in a text. You can either use one of the pre-made colors
* or make your own using RGB. {@link ChatColor#fromRGB(byte, byte, byte)}.
* <p>
* Immutable class.
* @deprecated For chat colors, use {@link TextColor} or {@link NamedTextColor}. For styles, use {@link TextDecoration}.
* For colors in other contexts, see {@link Color} or {@link DyeColor}.
*/
public final class ChatColor {
@Deprecated
public final class ChatColor implements StyleBuilderApplicable {
// Special
public static final ChatColor NO_COLOR = new ChatColor();
@ -272,6 +280,18 @@ public final class ChatColor {
return id;
}
/**
* Gets the Adventure text color from this chat color.
* @return the text color
*/
public @NotNull TextColor asTextColor() {
return TextColor.color(red, blue, green);
}
public @NotNull Color asColor() {
return new Color(red, green, blue);
}
@NotNull
@Override
public String toString() {
@ -295,4 +315,26 @@ public final class ChatColor {
return header + code + footer;
}
@Override
@Contract(mutates = "param")
public void styleApply(Style.@NotNull Builder style) {
if (this.isEmpty()) {
style.color(NamedTextColor.WHITE);
} else if (Objects.equals(this.codeName, "reset")) {
style.color(NamedTextColor.WHITE);
for (TextDecoration value : TextDecoration.NAMES.values()) {
style.decoration(value, TextDecoration.State.FALSE);
}
} else if (this.isSpecial() && this.codeName != null) {
TextDecoration decoration = TextDecoration.NAMES.value(this.codeName);
if (decoration != null) {
style.decorate(decoration);
}
} else {
style.color(TextColor.color(this.red, this.green, this.blue));
}
}
}

View File

@ -1,15 +1,24 @@
package net.minestom.server.chat;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.EntityType;
import net.minestom.server.item.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import java.util.UUID;
/**
* Represents a hover event for a specific portion of the message.
*
* @deprecated Use {@link HoverEvent}
*/
@Deprecated
public class ChatHoverEvent {
private final String action;
@ -37,12 +46,12 @@ public class ChatHoverEvent {
}
@Nullable
protected String getValue() {
public String getValue() {
return value;
}
@Nullable
protected JsonObject getValueObject() {
public JsonObject getValueObject() {
return valueObject;
}
@ -80,8 +89,14 @@ public class ChatHoverEvent {
*/
@NotNull
public static ChatHoverEvent showItem(@NotNull ItemStack itemStack) {
final String json = itemStack.toNBT().toSNBT();
return new ChatHoverEvent("show_item", json);
HoverEvent<HoverEvent.ShowItem> event = HoverEvent.showItem(itemStack.getMaterial().key(), itemStack.getAmount());
JsonObject obj = GsonComponentSerializer.gson().serializer().toJsonTree(Component.empty().hoverEvent(event)).getAsJsonObject();
obj = obj.get("hoverEvent").getAsJsonObject().get("contents").getAsJsonObject();
final String snbt = itemStack.getMeta().toSNBT();
obj.add("tag", new JsonPrimitive(snbt));
return new ChatHoverEvent("show_item", obj);
}
/**
@ -92,9 +107,14 @@ public class ChatHoverEvent {
*/
@NotNull
public static ChatHoverEvent showEntity(@NotNull Entity entity) {
NBTCompound compound = new NBTCompound()
.setString("id", entity.getUuid().toString())
.setString("type", entity.getEntityType().getNamespaceID());
return new ChatHoverEvent("show_entity", compound.toSNBT());
HoverEvent<HoverEvent.ShowEntity> event = HoverEvent.showEntity(entity.getEntityType().key(), entity.getUuid());
JsonObject obj = GsonComponentSerializer.gson().serializer().toJsonTree(Component.empty().hoverEvent(event)).getAsJsonObject();
return new ChatHoverEvent("show_entity", obj.get("hoverEvent").getAsJsonObject().get("contents").getAsJsonObject());
}
public static ChatHoverEvent showEntity(UUID uuid, EntityType entityType) {
HoverEvent<HoverEvent.ShowEntity> event = HoverEvent.showEntity(entityType.key(), uuid);
JsonObject obj = GsonComponentSerializer.gson().serializer().toJsonTree(Component.empty().hoverEvent(event)).getAsJsonObject();
return new ChatHoverEvent("show_entity", obj.get("hoverEvent").getAsJsonObject().get("contents").getAsJsonObject());
}
}

View File

@ -2,11 +2,14 @@ package net.minestom.server.chat;
import com.google.gson.*;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import org.jetbrains.annotations.NotNull;
/**
* Class used to convert JSON string to proper chat message representation.
* @deprecated Use {@link GsonComponentSerializer}
*/
@Deprecated
public final class ChatParser {
public static final char COLOR_CHAR = (char) 0xA7; // Represent the character '§'

View File

@ -2,6 +2,8 @@ package net.minestom.server.chat;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextColor;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
@ -16,7 +18,9 @@ import java.util.regex.Pattern;
* <p>
* To create one, you simply call one of the static methods like {@link #of(ChatColor, String)},
* you can then continue to append text with {@link #append(ChatColor, String)}.
* @deprecated Use {@link Component#text(String, TextColor)}
*/
@Deprecated
public class ColoredText extends JsonMessage {
private static final char SEPARATOR_START = '{';

View File

@ -3,6 +3,9 @@ package net.minestom.server.chat;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
@ -13,8 +16,10 @@ import java.util.Objects;
* Examples are {@link ColoredText} and {@link RichMessage}.
*
* @see <a href="https://wiki.vg/Chat">Chat Format</a>
* @deprecated Use {@link Component}
*/
public abstract class JsonMessage {
@Deprecated
public abstract class JsonMessage implements ComponentLike {
// true if the compiled string is up-to-date, false otherwise
private boolean updated;
@ -49,6 +54,15 @@ public abstract class JsonMessage {
return getTextMessage(getJsonObject()).toString();
}
@Override
public @NotNull Component asComponent() {
return GsonComponentSerializer.gson().deserializeFromTree(this.getJsonObject());
}
public static @NotNull JsonMessage fromComponent(@NotNull Component component) {
return new RawJsonMessage(GsonComponentSerializer.gson().serializer().toJsonTree(component).getAsJsonObject());
}
/**
* Gets the Json representation.
* <p>
@ -102,6 +116,7 @@ public abstract class JsonMessage {
return message;
}
@Deprecated
public static class RawJsonMessage extends JsonMessage {
private final JsonObject jsonObject;

View File

@ -2,6 +2,7 @@ package net.minestom.server.chat;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import net.kyori.adventure.text.Component;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -18,7 +19,9 @@ import java.util.List;
* You will need to call the static method to initialize the message {@link #of(ColoredText)},
* events can be assigned with {@link #setClickEvent(ChatClickEvent)} and {@link #setHoverEvent(ChatHoverEvent)}
* and new text element can also be appended {@link #append(ColoredText)}.
* @deprecated Use {@link Component}
*/
@Deprecated
public class RichMessage extends JsonMessage {
private final List<RichComponent> components = new ArrayList<>();
@ -159,7 +162,7 @@ public class RichMessage extends JsonMessage {
// The value is a JsonObject
hoverObject = new JsonObject();
hoverObject.addProperty("action", hoverEvent.getAction());
hoverObject.add("value", hoverEvent.getValueObject());
hoverObject.add("contents", hoverEvent.getValueObject());
} else {
// The value is a raw string
final String hoverValue = hoverEvent.getValue();

View File

@ -1,11 +1,14 @@
package net.minestom.server.chat;
import net.kyori.adventure.text.TranslatableComponent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Represents a translatable component which can be used in {@link ColoredText}.
* @deprecated Use {@link TranslatableComponent}
*/
@Deprecated
public class TranslatableText {
private final String code;

View File

@ -32,21 +32,22 @@ public class CollisionUtils {
@NotNull Vector velocityOut) {
// TODO handle collisions with nearby entities (should it be done here?)
final Instance instance = entity.getInstance();
final Chunk originChunk = entity.getChunk();
final Position currentPosition = entity.getPosition();
final BoundingBox boundingBox = entity.getBoundingBox();
Vector intermediaryPosition = new Vector();
final boolean yCollision = stepAxis(instance, currentPosition.toVector(), Y_AXIS, deltaPosition.getY(),
final boolean yCollision = stepAxis(instance, originChunk, currentPosition.toVector(), Y_AXIS, deltaPosition.getY(),
intermediaryPosition,
deltaPosition.getY() > 0 ? boundingBox.getTopFace() : boundingBox.getBottomFace()
);
final boolean xCollision = stepAxis(instance, intermediaryPosition, X_AXIS, deltaPosition.getX(),
final boolean xCollision = stepAxis(instance, originChunk, intermediaryPosition, X_AXIS, deltaPosition.getX(),
intermediaryPosition,
deltaPosition.getX() < 0 ? boundingBox.getLeftFace() : boundingBox.getRightFace()
);
final boolean zCollision = stepAxis(instance, intermediaryPosition, Z_AXIS, deltaPosition.getZ(),
final boolean zCollision = stepAxis(instance, originChunk, intermediaryPosition, Z_AXIS, deltaPosition.getZ(),
intermediaryPosition,
deltaPosition.getZ() > 0 ? boundingBox.getBackFace() : boundingBox.getFrontFace()
);
@ -80,7 +81,11 @@ public class CollisionUtils {
* @param corners the corners to check against
* @return true if a collision has been found
*/
private static boolean stepAxis(Instance instance, Vector startPosition, Vector axis, double stepAmount, Vector positionOut, Vector... corners) {
private static boolean stepAxis(Instance instance,
Chunk originChunk,
Vector startPosition, Vector axis,
double stepAmount, Vector positionOut,
Vector... corners) {
positionOut.copy(startPosition);
if (corners.length == 0)
return false; // avoid degeneracy in following computations
@ -99,7 +104,7 @@ public class CollisionUtils {
// used to determine if 'remainingLength' should be used
boolean collisionFound = false;
for (int i = 0; i < Math.abs(blockLength); i++) {
if (!stepOnce(instance, axis, sign, cornersCopy, cornerPositions)) {
if (!stepOnce(instance, originChunk, axis, sign, cornersCopy, cornerPositions)) {
collisionFound = true;
}
if (collisionFound) {
@ -111,7 +116,7 @@ public class CollisionUtils {
if (!collisionFound) {
Vector direction = new Vector();
direction.copy(axis);
collisionFound = !stepOnce(instance, direction, remainingLength, cornersCopy, cornerPositions);
collisionFound = !stepOnce(instance, originChunk, direction, remainingLength, cornersCopy, cornerPositions);
}
// find the corner which moved the least
@ -138,7 +143,9 @@ public class CollisionUtils {
* @param cornerPositions the corners, converted to BlockPosition (mutable)
* @return false if this method encountered a collision
*/
private static boolean stepOnce(Instance instance, Vector axis, double amount, Vector[] cornersCopy, BlockPosition[] cornerPositions) {
private static boolean stepOnce(Instance instance,
Chunk originChunk,
Vector axis, double amount, Vector[] cornersCopy, BlockPosition[] cornerPositions) {
final double sign = Math.signum(amount);
for (int cornerIndex = 0; cornerIndex < cornersCopy.length; cornerIndex++) {
Vector corner = cornersCopy[cornerIndex];
@ -148,10 +155,13 @@ public class CollisionUtils {
blockPos.setY((int) Math.floor(corner.getY()));
blockPos.setZ((int) Math.floor(corner.getZ()));
final Chunk chunk = instance.getChunkAt(blockPos);
if (!ChunkUtils.isLoaded(chunk)) {
// Collision at chunk border
return false;
Chunk chunk = originChunk;
if (!ChunkUtils.same(originChunk, blockPos.getX(), blockPos.getZ())) {
chunk = instance.getChunkAt(blockPos);
if (!ChunkUtils.isLoaded(chunk)) {
// Collision at chunk border
return false;
}
}
final short blockStateId = chunk.getBlockStateId(blockPos.getX(), blockPos.getY(), blockPos.getZ());

View File

@ -0,0 +1,203 @@
package net.minestom.server.color;
import net.kyori.adventure.util.RGBLike;
import net.minestom.server.chat.ChatColor;
import org.apache.commons.lang3.Validate;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
/**
* A general purpose class for representing colors.
*/
public class Color implements RGBLike {
private static final int BIT_MASK = 0xff;
private int red, green, blue;
/**
* Creates a color from an integer. This is done by reading each color component
* from the lowest order 24 bits of the integer, and creating a color from those
* components.
*
* @param rgb the integer
*/
public Color(int rgb) {
this((rgb >> 16) & BIT_MASK, (rgb >> 8) & BIT_MASK, rgb & BIT_MASK);
}
/**
* Creates a color from an RGB-like color.
*
* @param rgbLike the color
*/
public Color(RGBLike rgbLike) {
this(rgbLike.red(), rgbLike.blue(), rgbLike.green());
}
/**
* Creates a color from red, green, and blue components.
*
* @param red the red component
* @param green the green component
* @param blue the blue component
*
* @throws IllegalArgumentException if any component value is not between 0-255 (inclusive)
*/
public Color(int red, int green, int blue) {
Validate.isTrue(red >= 0 && red <= 255, "Red is not between 0-255: ", red);
Validate.isTrue(green >= 0 && green <= 255, "Green is not between 0-255: ", green);
Validate.isTrue(blue >= 0 && blue <= 255, "Blue is not between 0-255: ", blue);
this.red = red;
this.green = green;
this.blue = blue;
}
/**
* Gets the red component.
*
* @return red component, between 0-255 (inclusive)
*/
public int getRed() {
return this.red;
}
/**
* Creates a new Color object with specified component
*
* @param red the red component, from 0 to 255
*/
public void setRed(int red) {
Validate.isTrue(red >= 0 && red <= 255, "Red is not between 0-255: ", red);
this.red = red;
}
/**
* Gets the green component
*
* @return green component, from 0 to 255
*/
public int getGreen() {
return this.green;
}
/**
* Creates a new Color object with specified component
*
* @param green the red component, from 0 to 255
*/
public void setGreen(int green) {
Validate.isTrue(green >= 0 && green <= 255, "Green is not between 0-255: ", green);
this.green = green;
}
/**
* Gets the blue component
*
* @return blue component, from 0 to 255
*/
public int getBlue() {
return this.blue;
}
/**
* Sets the blue component of this color.
*
* @param blue the red component, from 0 to 255
*/
public void setBlue(int blue) {
Validate.isTrue(blue >= 0 && blue <= 255, "Blue is not between 0-255: ", blue);
this.blue = blue;
}
/**
* Gets the color as an RGB integer.
*
* @return An integer representation of this color, as 0xRRGGBB
*/
public int asRGB() {
int rgb = red;
rgb = (rgb << 8) + green;
return (rgb << 8) + blue;
}
/**
* Mixes this color with a series of other colors, as if they were combined in a
* crafting table. This function works out the average of each RGB component and then
* multiplies the components by a scale factor that is calculated from the average
* of all maximum values divided by the maximum of each average value. This is how
* Minecraft mixes colors.
*
* @param colors the colors
*/
public void mixWith(@NotNull RGBLike... colors) {
Validate.noNullElements(colors, "Colors cannot be null");
// store the current highest component
int max = Math.max(Math.max(this.red, this.green), this.blue);
// now combine all of the color components, adding to the max
for (RGBLike color : colors) {
this.red += color.red();
this.green += color.green();
this.blue += color.blue();
max += Math.max(Math.max(color.red(), color.green()), color.blue());
}
// work out the averages
float count = colors.length + 1;
float averageRed = this.red / count;
float averageGreen = this.green / count;
float averageBlue = this.blue / count;
float averageMax = max / count;
// work out the scale factor
float maximumOfAverages = Math.max(Math.max(averageRed, averageGreen), averageBlue);
float gainFactor = averageMax / maximumOfAverages;
// round and multiply
this.red = Math.round(averageRed * gainFactor);
this.blue = Math.round(averageBlue * gainFactor);
this.green = Math.round(averageGreen * gainFactor);
}
/**
* Gets the ChatColor representation of this color.
*
* @return the chat color
* @deprecated ChatColor is deprecated and should not be used
*/
@Deprecated
public ChatColor asLegacyChatColor() {
return ChatColor.fromRGB((byte) red, (byte) blue, (byte) green);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Color color = (Color) o;
return red == color.red && green == color.green && blue == color.blue;
}
@Override
public int hashCode() {
return Objects.hash(red, green, blue);
}
@Override
public int red() {
return this.red;
}
@Override
public int green() {
return this.green;
}
@Override
public int blue() {
return this.blue;
}
}

View File

@ -0,0 +1,69 @@
package net.minestom.server.color;
import net.kyori.adventure.util.RGBLike;
import org.jetbrains.annotations.NotNull;
/**
* Color values for dyes, wool and cloth items.
*/
public enum DyeColor implements RGBLike {
WHITE(new Color(0xF9FFFE), new Color(0xF0F0F0)),
ORANGE(new Color(0xF9801D), new Color(0xEB8844)),
MAGENTA(new Color(0xC74EBD), new Color(0xC354CD)),
LIGHT_BLUE(new Color(0x3AB3DA), new Color(0x6689D3)),
YELLOW(new Color(0xFED83D), new Color(0xDECF2A)),
LIME(new Color(0x80C71F), new Color(0x41CD34)),
PINK(new Color(0xF38BAA), new Color(0xD88198)),
GRAY(new Color(0x474F52), new Color(0x434343)),
LIGHT_GRAY(new Color(0x9D9D97), new Color(0xABABAB)),
CYAN(new Color(0x169C9C), new Color(0x287697)),
PURPLE(new Color(0x8932B8), new Color(0x7B2FBE)),
BLUE(new Color(0x3C44AA), new Color(0x253192)),
BROWN(new Color(0x835432), new Color(0x51301A)),
GREEN(new Color(0x5E7C16), new Color(0x3B511A)),
RED(new Color(0xB02E26), new Color(0xB3312C)),
BLACK(new Color(0x1D1D21), new Color(0x1E1B1B));
private final Color color;
private final Color firework;
DyeColor(Color color, Color firework) {
this.color = color;
this.firework = firework;
}
/**
* Gets the color that this dye represents.
*
* @return The {@link Color} that this dye represents
*/
@NotNull
public Color getColor() {
return this.color;
}
/**
* Gets the firework color that this dye represents.
*
* @return The {@link Color} that this dye represents
*/
@NotNull
public Color getFireworkColor() {
return this.firework;
}
@Override
public int red() {
return this.color.red();
}
@Override
public int green() {
return this.color.green();
}
@Override
public int blue() {
return this.color.blue();
}
}

View File

@ -16,6 +16,7 @@ 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.binary.BinaryWriter;
import net.minestom.server.utils.callback.CommandCallback;
import net.minestom.server.utils.validate.Check;
import org.apache.commons.lang3.StringUtils;
@ -327,7 +328,7 @@ public final class CommandManager {
true, false, tracking);
tabNode.name = tracking ? "tab_completion" : "args";
tabNode.parser = "brigadier:string";
tabNode.properties = packetWriter -> packetWriter.writeVarInt(2); // Greedy phrase
tabNode.properties = BinaryWriter.makeArray(packetWriter -> packetWriter.writeVarInt(2)); // Greedy phrase
tabNode.children = new int[0];
if (tracking) {
tabNode.suggestionsType = "minecraft:ask_server";
@ -545,7 +546,6 @@ public final class CommandManager {
literalNode.children = ArrayUtils.toArray(cmdChildren);
return literalNode;
}
@NotNull

View File

@ -1,5 +1,7 @@
package net.minestom.server.command;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import net.minestom.server.chat.JsonMessage;
import net.minestom.server.entity.Player;
import net.minestom.server.permission.PermissionHandler;
@ -10,21 +12,23 @@ import org.jetbrains.annotations.NotNull;
* <p>
* Main implementations are {@link Player} and {@link ConsoleSender}.
*/
public interface CommandSender extends PermissionHandler {
public interface CommandSender extends PermissionHandler, Audience {
/**
* Sends a raw string message.
*
* @param message the message to send
*/
void sendMessage(@NotNull String message);
default void sendMessage(@NotNull String message) {
this.sendMessage(Component.text(message));
}
/**
* Sends multiple raw string messages.
*
* @param messages the messages to send
*/
default void sendMessage(@NotNull String[] messages) {
default void sendMessage(@NotNull String @NotNull[] messages) {
for (String message : messages) {
sendMessage(message);
}
@ -35,13 +39,12 @@ public interface CommandSender extends PermissionHandler {
* If this is not a {@link Player}, only the content of the message will be sent as a string.
*
* @param text The {@link JsonMessage} to send.
*
* @deprecated Use {@link #sendMessage(Component)}
* */
@Deprecated
default void sendMessage(@NotNull JsonMessage text) {
if (this instanceof Player) {
this.sendMessage(text);
} else {
sendMessage(text.getRawMessage());
}
this.sendMessage(text.asComponent());
}
/**
@ -50,7 +53,7 @@ public interface CommandSender extends PermissionHandler {
* @return true if 'this' is a player, false otherwise
*/
default boolean isPlayer() {
return this instanceof Player;
return false;
}
/**
@ -59,7 +62,7 @@ public interface CommandSender extends PermissionHandler {
* @return true if 'this' is the console, false otherwise
*/
default boolean isConsole() {
return this instanceof ConsoleSender;
return false;
}
/**
@ -70,7 +73,7 @@ public interface CommandSender extends PermissionHandler {
* @see #isPlayer()
*/
default Player asPlayer() {
return (Player) this;
throw new ClassCastException("CommandSender is not a Player");
}
/**
@ -81,6 +84,6 @@ public interface CommandSender extends PermissionHandler {
* @see #isConsole()
*/
default ConsoleSender asConsole() {
return (ConsoleSender) this;
throw new ClassCastException("CommandSender is not the ConsoleSender");
}
}

View File

@ -1,5 +1,9 @@
package net.minestom.server.command;
import net.kyori.adventure.audience.MessageType;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.plain.PlainComponentSerializer;
import net.minestom.server.permission.Permission;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
@ -12,8 +16,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
* Represents the console when sending a command to the server.
*/
public class ConsoleSender implements CommandSender {
private final static Logger LOGGER = LoggerFactory.getLogger(ConsoleSender.class);
private static final PlainComponentSerializer PLAIN_SERIALIZER = PlainComponentSerializer.plain();
private static final Logger LOGGER = LoggerFactory.getLogger(ConsoleSender.class);
private final Set<Permission> permissions = new CopyOnWriteArraySet<>();
@ -22,9 +26,25 @@ public class ConsoleSender implements CommandSender {
LOGGER.info(message);
}
@Override
public void sendMessage(@NotNull Identity source, @NotNull Component message, @NotNull MessageType type) {
// we don't use the serializer here as we just need the plain text of the message
this.sendMessage(PLAIN_SERIALIZER.serialize(message));
}
@NotNull
@Override
public Set<Permission> getAllPermissions() {
return permissions;
}
@Override
public boolean isConsole() {
return true;
}
@Override
public ConsoleSender asConsole() {
return this;
}
}

View File

@ -1,6 +1,7 @@
package net.minestom.server.command;
import net.minestom.server.command.builder.CommandContext;
import net.kyori.adventure.audience.Audience;
import net.minestom.server.permission.Permission;
import org.jetbrains.annotations.NotNull;
@ -11,18 +12,14 @@ import java.util.Set;
/**
* Sender used in {@link CommandManager#executeServerCommand(String)}.
* <p>
* Be aware that {@link #sendMessage(String)} is empty on purpose because the purpose
* of this sender is to process the data of {@link CommandContext#getReturnData()}.
* Although this class implemented {@link CommandSender} and thus {@link Audience}, no
* data can be sent to this sender because it's purpose is to process the data of
* {@link CommandContext#getReturnData()}.
*/
public class ServerSender implements CommandSender {
private final Set<Permission> permissions = Collections.unmodifiableSet(new HashSet<>());
@Override
public void sendMessage(@NotNull String message) {
// Empty on purpose
}
@NotNull
@Override
public Set<Permission> getAllPermissions() {

View File

@ -21,6 +21,7 @@ import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
/**
* @deprecated renamed to {@link CommandContext}
@ -296,14 +297,17 @@ public class Arguments {
this.args.clear();
}
protected void retrieveDefaultValues(@Nullable Map<String, Object> defaultValuesMap) {
protected void retrieveDefaultValues(@Nullable Map<String, Supplier<Object>> defaultValuesMap) {
if (defaultValuesMap == null)
return;
for (Map.Entry<String, Object> entry : defaultValuesMap.entrySet()) {
for (Map.Entry<String, Supplier<Object>> entry : defaultValuesMap.entrySet()) {
final String key = entry.getKey();
if (!args.containsKey(key))
this.args.put(key, entry.getValue());
if (!args.containsKey(key)) {
final var supplier = entry.getValue();
this.args.put(key, supplier.get());
}
}
}

View File

@ -13,6 +13,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.function.Supplier;
/**
* Represents a command which has suggestion/auto-completion.
@ -130,9 +131,9 @@ public class Command {
* there can be multiple of them when optional arguments are used
*/
@NotNull
public Collection<CommandSyntax> addSyntax(@Nullable CommandCondition commandCondition,
@NotNull CommandExecutor executor,
@NotNull Argument<?>... args) {
public Collection<CommandSyntax> addConditionalSyntax(@Nullable CommandCondition commandCondition,
@NotNull CommandExecutor executor,
@NotNull Argument<?>... args) {
// Check optional argument(s)
boolean hasOptional = false;
{
@ -157,14 +158,14 @@ public class Command {
// the 'args' array starts by all the required arguments, followed by the optional ones
List<Argument<?>> requiredArguments = new ArrayList<>();
Map<String, Object> defaultValuesMap = new HashMap<>();
Map<String, Supplier<Object>> defaultValuesMap = new HashMap<>();
boolean optionalBranch = false;
int i = 0;
for (Argument<?> argument : args) {
final boolean isLast = ++i == args.length;
if (argument.isOptional()) {
// Set default value
defaultValuesMap.put(argument.getId(), argument.getDefaultValue());
defaultValuesMap.put(argument.getId(), (Supplier<Object>) argument.getDefaultValue());
if (!optionalBranch && !requiredArguments.isEmpty()) {
// First optional argument, create a syntax with current cached arguments
@ -196,11 +197,11 @@ public class Command {
/**
* Adds a new syntax without condition.
*
* @see #addSyntax(CommandCondition, CommandExecutor, Argument[])
* @see #addConditionalSyntax(CommandCondition, CommandExecutor, Argument[])
*/
@NotNull
public Collection<CommandSyntax> addSyntax(@NotNull CommandExecutor executor, @NotNull Argument<?>... args) {
return addSyntax(null, executor, args);
return addConditionalSyntax(null, executor, args);
}
/**
@ -249,7 +250,7 @@ public class Command {
* Gets all the syntaxes of this command.
*
* @return a collection containing all this command syntaxes
* @see #addSyntax(CommandCondition, CommandExecutor, Argument[])
* @see #addSyntax(CommandExecutor, Argument[])
*/
@NotNull
public Collection<CommandSyntax> getSyntaxes() {

View File

@ -60,6 +60,9 @@ public class CommandDispatcher {
}
this.commands.remove(command);
// Clear cache
this.cache.invalidateAll();
}
@NotNull
@ -176,6 +179,7 @@ public class CommandDispatcher {
// Empty syntax found
final CommandSyntax syntax = optionalSyntax.get();
parsedCommand.syntax = syntax;
parsedCommand.executor = syntax.getExecutor();
parsedCommand.context = new CommandContext(input);

View File

@ -8,6 +8,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Map;
import java.util.function.Supplier;
/**
* Represents a syntax in {@link Command}
@ -18,14 +19,14 @@ public class CommandSyntax {
private CommandCondition commandCondition;
private CommandExecutor executor;
private final Map<String, Object> defaultValuesMap;
private final Map<String, Supplier<Object>> defaultValuesMap;
private final Argument<?>[] args;
private final boolean suggestion;
protected CommandSyntax(@Nullable CommandCondition commandCondition,
@NotNull CommandExecutor commandExecutor,
@Nullable Map<String, Object> defaultValuesMap,
@Nullable Map<String, Supplier<Object>> defaultValuesMap,
@NotNull Argument<?>... args) {
this.commandCondition = commandCondition;
this.executor = commandExecutor;
@ -85,7 +86,7 @@ public class CommandSyntax {
}
@Nullable
protected Map<String, Object> getDefaultValuesMap() {
protected Map<String, Supplier<Object>> getDefaultValuesMap() {
return defaultValuesMap;
}

View File

@ -11,6 +11,8 @@ import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.function.Supplier;
/**
* An argument is meant to be parsed when added into a {@link Command}'s syntax with {@link Command#addSyntax(CommandExecutor, Argument[])}.
* <p>
@ -23,12 +25,12 @@ import org.jetbrains.annotations.Nullable;
public abstract class Argument<T> {
private final String id;
private final boolean allowSpace;
private final boolean useRemaining;
protected final boolean allowSpace;
protected final boolean useRemaining;
private ArgumentCallback callback;
private T defaultValue;
private Supplier<T> defaultValue;
private SuggestionCallback suggestionCallback;
@ -173,18 +175,13 @@ public abstract class Argument<T> {
return defaultValue != null;
}
/**
* Gets the default value of this argument.
*
* @return the argument default value, null if the argument is not optional
*/
@Nullable
public T getDefaultValue() {
public Supplier<T> getDefaultValue() {
return defaultValue;
}
/**
* Sets the default value of the argument.
* Sets the default value supplier of the argument.
* <p>
* A non-null value means that the argument can be put at the end of a syntax
* to act as an optional one.
@ -193,11 +190,21 @@ public abstract class Argument<T> {
* @return 'this' for chaining
*/
@NotNull
public Argument<T> setDefaultValue(@Nullable T defaultValue) {
public Argument<T> setDefaultValue(@Nullable Supplier<T> defaultValue) {
this.defaultValue = defaultValue;
return this;
}
/**
* @deprecated use {@link #setDefaultValue(Supplier)}
*/
@NotNull
@Deprecated
public Argument<T> setDefaultValue(@Nullable T defaultValue) {
this.defaultValue = () -> defaultValue;
return this;
}
@Nullable
public SuggestionCallback getSuggestionCallback() {
return suggestionCallback;
@ -213,6 +220,18 @@ public abstract class Argument<T> {
return suggestionCallback != null;
}
/**
* Maps this argument's output to another result.
*
* @param mapper The mapper to use (this argument's input = desired output)
* @param <O> The type of output expected.
* @return A new ArgumentMap that can get this complex object type.
*/
@Beta
public <O> @NotNull ArgumentMap<T, O> map(@NotNull ArgumentMap.Mapper<T, O> mapper) {
return new ArgumentMap<>(this, mapper);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View File

@ -5,6 +5,7 @@ import net.minestom.server.command.builder.NodeMaker;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import net.minestom.server.command.builder.suggestion.SuggestionCallback;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import net.minestom.server.utils.binary.BinaryWriter;
import net.minestom.server.utils.callback.validator.StringArrayValidator;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
@ -47,9 +48,9 @@ public class ArgumentDynamicStringArray extends Argument<String[]> {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, true);
argumentNode.parser = "brigadier:string";
argumentNode.properties = packetWriter -> {
argumentNode.properties = BinaryWriter.makeArray(packetWriter -> {
packetWriter.writeVarInt(2); // Greedy phrase
};
});
argumentNode.suggestionsType = "minecraft:ask_server";
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});

View File

@ -6,6 +6,7 @@ import net.minestom.server.command.builder.arguments.minecraft.SuggestionType;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import net.minestom.server.command.builder.suggestion.SuggestionCallback;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import net.minestom.server.utils.binary.BinaryWriter;
import net.minestom.server.utils.callback.validator.StringValidator;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
@ -56,9 +57,9 @@ public class ArgumentDynamicWord extends Argument<String> {
final SuggestionType suggestionType = this.getSuggestionType();
argumentNode.parser = "brigadier:string";
argumentNode.properties = packetWriter -> {
argumentNode.properties = BinaryWriter.makeArray(packetWriter -> {
packetWriter.writeVarInt(0); // Single word
};
});
argumentNode.suggestionsType = suggestionType.getIdentifier();
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});

View File

@ -6,7 +6,7 @@ import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import org.jetbrains.annotations.NotNull;
import java.util.Locale;
import java.util.function.Function;
import java.util.function.UnaryOperator;
@SuppressWarnings("rawtypes")
public class ArgumentEnum<E extends Enum> extends Argument<E> {
@ -61,9 +61,9 @@ public class ArgumentEnum<E extends Enum> extends Argument<E> {
LOWER_CASED(name -> name.toLowerCase(Locale.ROOT)),
UPPER_CASED(name -> name.toUpperCase(Locale.ROOT));
private final Function<String, String> formatter;
private final UnaryOperator<String> formatter;
Format(Function<String, String> formatter) {
Format(@NotNull UnaryOperator<String> formatter) {
this.formatter = formatter;
}
}

View File

@ -0,0 +1,57 @@
package net.minestom.server.command.builder.arguments;
import net.minestom.server.command.builder.NodeMaker;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import org.jetbrains.annotations.NotNull;
/**
* Represents an argument that maps an existing argument to a value.
*
* @param <I> The input (any object)
* @param <O> The output (any object)
*/
public class ArgumentMap<I, O> extends Argument<O> {
final Argument<I> argument;
final Mapper<I, O> mapper;
protected ArgumentMap(@NotNull Argument<I> argument, @NotNull Mapper<I, O> mapper) {
super(argument.getId(), argument.allowSpace(), argument.useRemaining());
this.argument = argument;
this.mapper = mapper;
}
@Override
public @NotNull O parse(@NotNull String input) throws ArgumentSyntaxException {
return mapper.accept(argument.parse(input));
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
argument.processNodes(nodeMaker, executable);
}
/**
* Represents a lambda that can turn an input into an output
* that also allows the throwing of ArgumentSyntaxException
*
* @param <I> The input expected from the Argument
* @param <O> The desired output type from this lambda.
*/
@FunctionalInterface
public interface Mapper<I, O> {
/**
* Accepts I data from the argument and returns O output
*
* @param i The input processed from an argument
* @return The complex data type that came as a result from this argument
* @throws ArgumentSyntaxException If the input can not be turned into the desired output
* (E.X. an invalid extension name)
*/
O accept(I i) throws ArgumentSyntaxException;
}
}

View File

@ -4,6 +4,7 @@ import io.netty.util.internal.StringUtil;
import net.minestom.server.command.builder.NodeMaker;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import net.minestom.server.utils.binary.BinaryWriter;
import org.apache.commons.text.StringEscapeUtils;
import org.jetbrains.annotations.NotNull;
@ -35,9 +36,9 @@ public class ArgumentString extends Argument<String> {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = "brigadier:string";
argumentNode.properties = packetWriter -> {
argumentNode.properties = BinaryWriter.makeArray(packetWriter -> {
packetWriter.writeVarInt(1); // Quotable phrase
};
});
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}

View File

@ -2,6 +2,7 @@ package net.minestom.server.command.builder.arguments;
import net.minestom.server.command.builder.NodeMaker;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import net.minestom.server.utils.binary.BinaryWriter;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
@ -29,9 +30,9 @@ public class ArgumentStringArray extends Argument<String[]> {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = "brigadier:string";
argumentNode.properties = packetWriter -> {
argumentNode.properties = BinaryWriter.makeArray(packetWriter -> {
packetWriter.writeVarInt(2); // Greedy phrase
};
});
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}

View File

@ -1,5 +1,6 @@
package net.minestom.server.command.builder.arguments;
import com.google.common.annotations.Beta;
import net.minestom.server.command.builder.arguments.minecraft.*;
import net.minestom.server.command.builder.arguments.minecraft.registry.*;
import net.minestom.server.command.builder.arguments.number.ArgumentDouble;
@ -9,6 +10,7 @@ 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 net.minestom.server.command.builder.parser.ArgumentParser;
import org.jetbrains.annotations.NotNull;
/**
@ -134,6 +136,13 @@ public class ArgumentType {
return new ArgumentParticle(id);
}
/**
* @see ArgumentResourceLocation
*/
public static ArgumentResourceLocation ResourceLocation(@NotNull String id) {
return new ArgumentResourceLocation(id);
}
/**
* @see ArgumentPotionEffect
*/
@ -190,6 +199,13 @@ public class ArgumentType {
return new ArgumentComponent(id);
}
/**
* @see ArgumentUUID
*/
public static ArgumentUUID UUID(@NotNull String id) {
return new ArgumentUUID(id);
}
/**
* @see ArgumentNbtTag
*/
@ -225,6 +241,18 @@ public class ArgumentType {
return new ArgumentRelativeVec2(id);
}
/**
* Generates arguments from a string format.
* <p>
* Example: "Entity&lt;targets&gt; Integer&lt;number&gt;"
* <p>
* Note: this feature is in beta and is very likely to change depending on feedback.
*/
@Beta
public static Argument<?>[] generate(@NotNull String format) {
return ArgumentParser.generate(format);
}
/**
* @see ArgumentLoop
* @deprecated brigadier does not support long

View File

@ -3,6 +3,7 @@ package net.minestom.server.command.builder.arguments;
import net.minestom.server.command.builder.NodeMaker;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import net.minestom.server.utils.binary.BinaryWriter;
import net.minestom.server.utils.validate.Check;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
@ -90,9 +91,9 @@ public class ArgumentWord extends Argument<String> {
// Can be any word, add only one argument node
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = "brigadier:string";
argumentNode.properties = packetWriter -> {
argumentNode.properties = BinaryWriter.makeArray(packetWriter -> {
packetWriter.writeVarInt(0); // Single word
};
});
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}
}

View File

@ -1,6 +1,7 @@
package net.minestom.server.command.builder.arguments.minecraft;
import net.minestom.server.chat.ChatColor;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.Style;
import net.minestom.server.command.builder.NodeMaker;
import net.minestom.server.command.builder.arguments.Argument;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
@ -8,11 +9,12 @@ import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import org.jetbrains.annotations.NotNull;
/**
* Represents an argument which will give you a {@link ChatColor}.
* Represents an argument which will give you a {@link Style} containing the colour or no
* colour if the argument was {@code reset}.
* <p>
* Example: red, white, reset
*/
public class ArgumentColor extends Argument<ChatColor> {
public class ArgumentColor extends Argument<Style> {
public static final int UNDEFINED_COLOR = -2;
@ -22,12 +24,21 @@ public class ArgumentColor extends Argument<ChatColor> {
@NotNull
@Override
public ChatColor parse(@NotNull String input) throws ArgumentSyntaxException {
final ChatColor color = ChatColor.fromName(input);
if (color == ChatColor.NO_COLOR)
throw new ArgumentSyntaxException("Undefined color", input, UNDEFINED_COLOR);
public Style parse(@NotNull String input) throws ArgumentSyntaxException {
String uppercaseInput = input.toUpperCase();
return color;
// check for colour
NamedTextColor color = NamedTextColor.NAMES.value(uppercaseInput);
if (color != null) {
return Style.style(color);
}
// check for reset
if (uppercaseInput.equals("RESET")) {
return Style.empty();
}
throw new ArgumentSyntaxException("Undefined color", input, UNDEFINED_COLOR);
}
@Override

View File

@ -1,14 +1,15 @@
package net.minestom.server.command.builder.arguments.minecraft;
import com.google.gson.JsonParseException;
import net.minestom.server.chat.JsonMessage;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.minestom.server.command.builder.NodeMaker;
import net.minestom.server.command.builder.arguments.Argument;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import org.jetbrains.annotations.NotNull;
public class ArgumentComponent extends Argument<JsonMessage> {
public class ArgumentComponent extends Argument<Component> {
public static final int INVALID_JSON_ERROR = 1;
@ -18,9 +19,9 @@ public class ArgumentComponent extends Argument<JsonMessage> {
@NotNull
@Override
public JsonMessage parse(@NotNull String input) throws ArgumentSyntaxException {
public Component parse(@NotNull String input) throws ArgumentSyntaxException {
try {
return new JsonMessage.RawJsonMessage(input);
return GsonComponentSerializer.gson().deserialize(input);
} catch (JsonParseException e) {
throw new ArgumentSyntaxException("Invalid JSON", input, INVALID_JSON_ERROR);
}

View File

@ -7,6 +7,7 @@ import net.minestom.server.entity.EntityType;
import net.minestom.server.entity.GameMode;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import net.minestom.server.registry.Registries;
import net.minestom.server.utils.binary.BinaryWriter;
import net.minestom.server.utils.entity.EntityFinder;
import net.minestom.server.utils.math.IntRange;
import org.apache.commons.lang3.StringUtils;
@ -75,16 +76,16 @@ public class ArgumentEntity extends Argument<EntityFinder> {
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = "minecraft:entity";
argumentNode.properties = packetWriter -> {
argumentNode.properties = BinaryWriter.makeArray(packetWriter -> {
byte mask = 0;
if (this.isOnlySingleEntity()) {
mask += 1;
mask |= 0x01;
}
if (this.isOnlyPlayers()) {
mask += 2;
mask |= 0x02;
}
packetWriter.writeByte(mask);
};
});
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}

View File

@ -42,13 +42,11 @@ public class ArgumentItemStack extends Argument<ItemStack> {
if (nbtIndex == -1) {
// Only item name
final Material material = Registries.getMaterial(input);
return new ItemStack(material, (byte) 1);
return ItemStack.of(material);
} else {
final String materialName = input.substring(0, nbtIndex);
final Material material = Registries.getMaterial(materialName);
ItemStack itemStack = new ItemStack(material, (byte) 1);
final String sNBT = input.substring(nbtIndex).replace("\\\"", "\"");
NBTCompound compound;
@ -58,9 +56,7 @@ public class ArgumentItemStack extends Argument<ItemStack> {
throw new ArgumentSyntaxException("Item NBT is invalid", input, INVALID_NBT);
}
NBTUtils.loadDataIntoItem(itemStack, compound);
return itemStack;
return NBTUtils.loadItem(material, 1, compound);
}
}

View File

@ -0,0 +1,34 @@
package net.minestom.server.command.builder.arguments.minecraft;
import net.minestom.server.command.builder.NodeMaker;
import net.minestom.server.command.builder.arguments.Argument;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
public class ArgumentResourceLocation extends Argument<String> {
public static final int SPACE_ERROR = 1;
public ArgumentResourceLocation(@NotNull String id) {
super(id);
}
@NotNull
@Override
public String parse(@NotNull String input) throws ArgumentSyntaxException {
if (input.contains(StringUtils.SPACE))
throw new ArgumentSyntaxException("Resource location cannot contain space character", input, SPACE_ERROR);
return input;
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = "minecraft:resource_location";
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}
}

View File

@ -0,0 +1,36 @@
package net.minestom.server.command.builder.arguments.minecraft;
import net.minestom.server.command.builder.NodeMaker;
import net.minestom.server.command.builder.arguments.Argument;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
public class ArgumentUUID extends Argument<UUID> {
public static final int INVALID_UUID = -1;
public ArgumentUUID(@NotNull String id) {
super(id);
}
@NotNull
@Override
public UUID parse(@NotNull String input) throws ArgumentSyntaxException {
try {
return UUID.fromString(input);
} catch (IllegalArgumentException exception) {
throw new ArgumentSyntaxException("Invalid UUID", input, INVALID_UUID);
}
}
@Override
public void processNodes(@NotNull NodeMaker nodeMaker, boolean executable) {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = "minecraft:uuid";
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}
}

View File

@ -3,6 +3,7 @@ package net.minestom.server.command.builder.arguments.number;
import net.minestom.server.command.builder.NodeMaker;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import net.minestom.server.utils.binary.BinaryWriter;
import org.jetbrains.annotations.NotNull;
public class ArgumentDouble extends ArgumentNumber<Double> {
@ -45,13 +46,13 @@ public class ArgumentDouble extends ArgumentNumber<Double> {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = "brigadier:double";
argumentNode.properties = packetWriter -> {
argumentNode.properties = BinaryWriter.makeArray(packetWriter -> {
packetWriter.writeByte(getNumberProperties());
if (this.hasMin())
packetWriter.writeDouble(this.getMin());
if (this.hasMax())
packetWriter.writeDouble(this.getMax());
};
});
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}

View File

@ -3,6 +3,7 @@ package net.minestom.server.command.builder.arguments.number;
import net.minestom.server.command.builder.NodeMaker;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import net.minestom.server.utils.binary.BinaryWriter;
import org.jetbrains.annotations.NotNull;
public class ArgumentFloat extends ArgumentNumber<Float> {
@ -45,13 +46,13 @@ public class ArgumentFloat extends ArgumentNumber<Float> {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = "brigadier:float";
argumentNode.properties = packetWriter -> {
argumentNode.properties = BinaryWriter.makeArray(packetWriter -> {
packetWriter.writeByte(getNumberProperties());
if (this.hasMin())
packetWriter.writeFloat(this.getMin());
if (this.hasMax())
packetWriter.writeFloat(this.getMax());
};
});
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}

View File

@ -3,6 +3,7 @@ package net.minestom.server.command.builder.arguments.number;
import net.minestom.server.command.builder.NodeMaker;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import net.minestom.server.utils.binary.BinaryWriter;
import org.jetbrains.annotations.NotNull;
public class ArgumentInteger extends ArgumentNumber<Integer> {
@ -36,13 +37,13 @@ public class ArgumentInteger extends ArgumentNumber<Integer> {
DeclareCommandsPacket.Node argumentNode = simpleArgumentNode(this, executable, false, false);
argumentNode.parser = "brigadier:integer";
argumentNode.properties = packetWriter -> {
argumentNode.properties = BinaryWriter.makeArray(packetWriter -> {
packetWriter.writeByte(getNumberProperties());
if (this.hasMin())
packetWriter.writeInt(this.getMin());
if (this.hasMax())
packetWriter.writeInt(this.getMax());
};
});
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}

View File

@ -3,6 +3,7 @@ package net.minestom.server.command.builder.arguments.number;
import net.minestom.server.command.builder.NodeMaker;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import net.minestom.server.utils.binary.BinaryWriter;
import org.jetbrains.annotations.NotNull;
public class ArgumentLong extends ArgumentNumber<Long> {
@ -38,13 +39,13 @@ public class ArgumentLong extends ArgumentNumber<Long> {
// TODO maybe use ArgumentLiteral/ArgumentWord and impose long restriction server side?
argumentNode.parser = "brigadier:integer";
argumentNode.properties = packetWriter -> {
argumentNode.properties = BinaryWriter.makeArray(packetWriter -> {
packetWriter.writeByte(getNumberProperties());
if (this.hasMin())
packetWriter.writeInt(this.getMin().intValue());
if (this.hasMax())
packetWriter.writeInt(this.getMax().intValue());
};
});
nodeMaker.addNodes(new DeclareCommandsPacket.Node[]{argumentNode});
}

View File

@ -22,10 +22,10 @@ public interface CommandCondition {
* 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 sender 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);
boolean canUse(@NotNull CommandSender sender, @Nullable String commandString);
}

View File

@ -0,0 +1,25 @@
package net.minestom.server.command.builder.condition;
import net.kyori.adventure.text.Component;
import net.minestom.server.command.CommandSender;
/**
* Common command conditions
*/
public class Conditions {
public static boolean playerOnly(CommandSender sender, String commandString) {
if (!sender.isPlayer()) {
sender.sendMessage(Component.text("The command is only available for players"));
return false;
}
return true;
}
public static boolean consoleOnly(CommandSender sender, String commandString) {
if (!sender.isConsole()) {
sender.sendMessage(Component.text("The command is only available form the console"));
return false;
}
return true;
}
}

Some files were not shown because too many files have changed in this diff Show More