mirror of https://github.com/Minestom/Minestom.git
Compare commits
48 Commits
d106c68b96
...
95c5f6675f
Author | SHA1 | Date |
---|---|---|
mworzala | 95c5f6675f | |
mworzala | c29fe5a10a | |
mworzala | 3fcae5013d | |
mworzala | 8aee481e1b | |
mworzala | bcaa7df23d | |
mworzala | 0400af8013 | |
mworzala | 1cb848d1b9 | |
mworzala | 1d54534562 | |
mworzala | adeb2c67a3 | |
mworzala | 9ff67e4025 | |
mworzala | 5036b72be4 | |
mworzala | 90d18c2a95 | |
mworzala | 1246fa57d7 | |
mworzala | a50014ad8c | |
mworzala | 964b519146 | |
mworzala | ff0d121937 | |
mworzala | 49f8ae1223 | |
mworzala | f9c835ed6c | |
mworzala | 4e6c15189c | |
mworzala | 2b974d95f9 | |
mworzala | 3fdd9ab9bb | |
mworzala | c873d72f64 | |
mworzala | f1f1246230 | |
mworzala | a9e056a119 | |
mworzala | 690f828c3c | |
mworzala | ac18b3fb38 | |
mworzala | e8d4f79ee3 | |
mworzala | d6d2e9c3e7 | |
mworzala | 58367bb6de | |
mworzala | 721f70a28e | |
mworzala | 62cb99a524 | |
mworzala | 5188c15245 | |
themode | 41b442eab8 | |
themode | 8bb3ece803 | |
themode | bb31cf8b20 | |
GoldenStack | 755f934448 | |
GoldenStack | d417f42b74 | |
GoldenStack | 511c3d3e2e | |
themode | ea7ef09be7 | |
themode | f33565a82e | |
themode | 401d5b57a7 | |
themode | a350c29296 | |
themode | a0c915a5c9 | |
GoldenStack | 84ead9b25c | |
GoldenStack | 04893721b1 | |
mworzala | 129fd8ca0f | |
mworzala | f76d421744 | |
DeidaraMC | e28adbca3e |
|
@ -36,8 +36,7 @@ allprojects {
|
|||
withSourcesJar()
|
||||
withJavadocJar()
|
||||
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
toolchain.languageVersion = JavaLanguageVersion.of(21)
|
||||
}
|
||||
|
||||
tasks.withType<Zip> {
|
||||
|
@ -72,10 +71,7 @@ dependencies {
|
|||
api(libs.jetbrainsAnnotations)
|
||||
api(libs.bundles.adventure)
|
||||
api(libs.hydrazine)
|
||||
api(libs.bundles.kotlin)
|
||||
api(libs.bundles.hephaistos)
|
||||
implementation(libs.minestomData)
|
||||
implementation(libs.dependencyGetter)
|
||||
|
||||
// Performance/data structures
|
||||
implementation(libs.caffeine)
|
||||
|
|
|
@ -2,6 +2,7 @@ package net.minestom.codegen;
|
|||
|
||||
import net.minestom.codegen.color.DyeColorGenerator;
|
||||
import net.minestom.codegen.fluid.FluidGenerator;
|
||||
import net.minestom.codegen.recipe.RecipeTypeGenerator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -18,35 +19,30 @@ public class Generators {
|
|||
}
|
||||
File outputFolder = new File(args[0]);
|
||||
|
||||
|
||||
// Generate DyeColors
|
||||
// Special generators
|
||||
new DyeColorGenerator(resource("dye_colors.json"), outputFolder).generate();
|
||||
new RecipeTypeGenerator(resource("recipe_types.json"), outputFolder).generate();
|
||||
|
||||
|
||||
// Generic protocol object
|
||||
var generator = new CodeGenerator(outputFolder);
|
||||
generator.generate(resource("blocks.json"), "net.minestom.server.instance.block", "Block", "BlockImpl", "Blocks");
|
||||
generator.generate(resource("items.json"), "net.minestom.server.item", "Material", "MaterialImpl", "Materials");
|
||||
generator.generate(resource("entities.json"), "net.minestom.server.entity", "EntityType", "EntityTypeImpl", "EntityTypes");
|
||||
generator.generate(resource("biomes.json"), "net.minestom.server.world.biomes", "Biome", "BiomeImpl", "Biomes");
|
||||
generator.generate(resource("enchantments.json"), "net.minestom.server.item", "Enchantment", "EnchantmentImpl", "Enchantments");
|
||||
generator.generate(resource("enchantments.json"), "net.minestom.server.item.enchant", "Enchantment", "EnchantmentImpl", "Enchantments");
|
||||
generator.generate(resource("potion_effects.json"), "net.minestom.server.potion", "PotionEffect", "PotionEffectImpl", "PotionEffects");
|
||||
generator.generate(resource("potions.json"), "net.minestom.server.potion", "PotionType", "PotionTypeImpl", "PotionTypes");
|
||||
generator.generate(resource("particles.json"), "net.minestom.server.particle", "Particle", "ParticleImpl", "Particles");
|
||||
generator.generate(resource("sounds.json"), "net.minestom.server.sound", "SoundEvent", "SoundEventImpl", "SoundEvents");
|
||||
generator.generate(resource("sounds.json"), "net.minestom.server.sound", "SoundEvent", "BuiltinSoundEvent", "SoundEvents");
|
||||
generator.generate(resource("custom_statistics.json"), "net.minestom.server.statistic", "StatisticType", "StatisticTypeImpl", "StatisticTypes");
|
||||
generator.generate(resource("damage_types.json"), "net.minestom.server.entity.damage", "DamageType", "DamageTypeImpl", "DamageTypes");
|
||||
generator.generate(resource("trim_materials.json"), "net.minestom.server.item.armor", "TrimMaterial", "TrimMaterialImpl", "TrimMaterials");
|
||||
generator.generate(resource("trim_patterns.json"), "net.minestom.server.item.armor", "TrimPattern", "TrimPatternImpl", "TrimPatterns");
|
||||
|
||||
generator.generate(resource("attributes.json"), "net.minestom.server.entity.attribute", "Attribute", "AttributeImpl", "Attributes");
|
||||
|
||||
// Generate fluids
|
||||
new FluidGenerator(resource("fluids.json"), outputFolder).generate();
|
||||
|
||||
// TODO: Generate attributes
|
||||
// new AttributeGenerator(
|
||||
// new File(inputFolder, targetVersion + "_attributes.json"),
|
||||
// outputFolder
|
||||
// ).generate();
|
||||
// TODO: Generate villager professions
|
||||
// new VillagerProfessionGenerator(
|
||||
// new File(inputFolder, targetVersion + "_villager_professions.json"),
|
||||
|
|
|
@ -1,249 +0,0 @@
|
|||
package net.minestom.codegen.attribute;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.squareup.javapoet.*;
|
||||
import net.minestom.codegen.MinestomCodeGenerator;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public final class AttributeGenerator extends MinestomCodeGenerator {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AttributeGenerator.class);
|
||||
private final InputStream attributesFile;
|
||||
private final File outputFolder;
|
||||
|
||||
public AttributeGenerator(@Nullable InputStream attributesFile, @NotNull File outputFolder) {
|
||||
this.attributesFile = attributesFile;
|
||||
this.outputFolder = outputFolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate() {
|
||||
if (attributesFile == null) {
|
||||
LOGGER.error("Failed to find attributes.json.");
|
||||
LOGGER.error("Stopped code generation for attributes.");
|
||||
return;
|
||||
}
|
||||
if (!outputFolder.exists() && !outputFolder.mkdirs()) {
|
||||
LOGGER.error("Output folder for code generation does not exist and could not be created.");
|
||||
return;
|
||||
}
|
||||
// Important classes we use alot
|
||||
ClassName namespaceIDClassName = ClassName.get("net.minestom.server.utils", "NamespaceID");
|
||||
ClassName registryClassName = ClassName.get("net.minestom.server.registry", "Registry");
|
||||
|
||||
JsonArray attributes = GSON.fromJson(new InputStreamReader(attributesFile), JsonArray.class);
|
||||
List<JavaFile> filesToWrite = new ArrayList<>();
|
||||
|
||||
ClassName attributeClassName = ClassName.get("net.minestom.server.attribute", "Attribute");
|
||||
|
||||
// Attribute
|
||||
TypeSpec.Builder attributeClass = TypeSpec.classBuilder(attributeClassName)
|
||||
.addSuperinterface(ClassName.get("net.kyori.adventure.key", "Keyed"))
|
||||
.addModifiers(Modifier.PUBLIC).addJavadoc("AUTOGENERATED by " + getClass().getSimpleName());
|
||||
attributeClass.addField(
|
||||
FieldSpec.builder(namespaceIDClassName, "id")
|
||||
.addModifiers(Modifier.PRIVATE, Modifier.FINAL).addAnnotation(NotNull.class).build()
|
||||
);
|
||||
attributeClass.addField(
|
||||
FieldSpec.builder(TypeName.DOUBLE, "defaultValue")
|
||||
.addModifiers(Modifier.PRIVATE, Modifier.FINAL).build()
|
||||
);
|
||||
attributeClass.addField(
|
||||
FieldSpec.builder(TypeName.BOOLEAN, "clientSyncable")
|
||||
.addModifiers(Modifier.PRIVATE, Modifier.FINAL).build()
|
||||
);
|
||||
attributeClass.addMethod(
|
||||
MethodSpec.constructorBuilder()
|
||||
.addParameter(ParameterSpec.builder(namespaceIDClassName, "id").addAnnotation(NotNull.class).build())
|
||||
.addParameter(ParameterSpec.builder(TypeName.BOOLEAN, "clientSyncable").build())
|
||||
.addParameter(ParameterSpec.builder(TypeName.DOUBLE, "defaultValue").build())
|
||||
.addStatement("this.id = id")
|
||||
.addStatement("this.clientSyncable = clientSyncable")
|
||||
.addStatement("this.defaultValue = defaultValue")
|
||||
.addModifiers(Modifier.PROTECTED)
|
||||
.build()
|
||||
);
|
||||
// Override key method (adventure)
|
||||
attributeClass.addMethod(
|
||||
MethodSpec.methodBuilder("key")
|
||||
.returns(ClassName.get("net.kyori.adventure.key", "Key"))
|
||||
.addAnnotation(Override.class)
|
||||
.addAnnotation(NotNull.class)
|
||||
.addStatement("return this.id")
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.build()
|
||||
);
|
||||
// getId method
|
||||
attributeClass.addMethod(
|
||||
MethodSpec.methodBuilder("getId")
|
||||
.returns(namespaceIDClassName)
|
||||
.addAnnotation(NotNull.class)
|
||||
.addStatement("return this.id")
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.build()
|
||||
);
|
||||
// getDefaultValue
|
||||
attributeClass.addMethod(
|
||||
MethodSpec.methodBuilder("getDefaultValue")
|
||||
.returns(TypeName.DOUBLE)
|
||||
.addStatement("return this.defaultValue")
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.build()
|
||||
);
|
||||
// isClientSyncable
|
||||
attributeClass.addMethod(
|
||||
MethodSpec.methodBuilder("isClientSyncable")
|
||||
.returns(TypeName.BOOLEAN)
|
||||
.addStatement("return this.clientSyncable")
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.build()
|
||||
);
|
||||
// isShared
|
||||
attributeClass.addMethod(
|
||||
MethodSpec.methodBuilder("isShared")
|
||||
.addAnnotation(Deprecated.class)
|
||||
.returns(TypeName.BOOLEAN)
|
||||
.addStatement("return this.clientSyncable")
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.build()
|
||||
);
|
||||
// values method
|
||||
attributeClass.addMethod(
|
||||
MethodSpec.methodBuilder("values")
|
||||
.addAnnotation(NotNull.class)
|
||||
.returns(ParameterizedTypeName.get(ClassName.get(List.class), attributeClassName))
|
||||
.addStatement("return $T.ATTRIBUTE_REGISTRY.values()", registryClassName)
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.build()
|
||||
);
|
||||
// toString method
|
||||
attributeClass.addMethod(
|
||||
MethodSpec.methodBuilder("toString")
|
||||
.addAnnotation(NotNull.class)
|
||||
.addAnnotation(Override.class)
|
||||
.returns(String.class)
|
||||
// this resolves to [Namespace]
|
||||
.addStatement("return \"[\" + this.id + \"]\"")
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.build()
|
||||
);
|
||||
// Creating ClampedAttribute
|
||||
ClassName clampedAttributeClassName = ClassName.get("net.minestom.server.attribute", "ClampedAttribute");
|
||||
|
||||
TypeSpec.Builder clampedAttributeClass = TypeSpec.classBuilder(clampedAttributeClassName)
|
||||
.superclass(attributeClassName)
|
||||
.addModifiers(Modifier.PUBLIC).addJavadoc("AUTOGENERATED by " + getClass().getSimpleName());
|
||||
clampedAttributeClass.addField(
|
||||
FieldSpec.builder(TypeName.DOUBLE, "minValue")
|
||||
.addModifiers(Modifier.PRIVATE, Modifier.FINAL).build()
|
||||
);
|
||||
clampedAttributeClass.addField(
|
||||
FieldSpec.builder(TypeName.DOUBLE, "maxValue")
|
||||
.addModifiers(Modifier.PRIVATE, Modifier.FINAL).build()
|
||||
);
|
||||
clampedAttributeClass.addMethod(
|
||||
MethodSpec.constructorBuilder()
|
||||
.addParameter(ParameterSpec.builder(namespaceIDClassName, "id").addAnnotation(NotNull.class).build())
|
||||
.addParameter(ParameterSpec.builder(TypeName.BOOLEAN, "clientSyncable").build())
|
||||
.addParameter(ParameterSpec.builder(TypeName.DOUBLE, "defaultValue").build())
|
||||
.addParameter(ParameterSpec.builder(TypeName.DOUBLE, "minValue").build())
|
||||
.addParameter(ParameterSpec.builder(TypeName.DOUBLE, "maxValue").build())
|
||||
.addStatement("super(id, clientSyncable, defaultValue)")
|
||||
.addStatement("this.minValue = minValue")
|
||||
.addStatement("this.maxValue = maxValue")
|
||||
.addModifiers(Modifier.PROTECTED)
|
||||
.build()
|
||||
);
|
||||
// getMinValue
|
||||
clampedAttributeClass.addMethod(
|
||||
MethodSpec.methodBuilder("getMinValue")
|
||||
.returns(TypeName.DOUBLE)
|
||||
.addStatement("return this.minValue")
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.build()
|
||||
);
|
||||
// getMaxValue
|
||||
clampedAttributeClass.addMethod(
|
||||
MethodSpec.methodBuilder("getMaxValue")
|
||||
.returns(TypeName.DOUBLE)
|
||||
.addStatement("return this.maxValue")
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.build()
|
||||
);
|
||||
|
||||
CodeBlock.Builder staticBlock = CodeBlock.builder();
|
||||
// Use data
|
||||
for (JsonElement a : attributes) {
|
||||
JsonObject attribute = a.getAsJsonObject();
|
||||
String attributeName = attribute.get("name").getAsString();
|
||||
|
||||
JsonObject range = attribute.getAsJsonObject("range");
|
||||
if (range == null) {
|
||||
// Normal attribute
|
||||
attributeClass.addField(
|
||||
FieldSpec.builder(
|
||||
attributeClassName,
|
||||
attributeName
|
||||
).initializer(
|
||||
"new $T($T.from($S), $L, $L)",
|
||||
attributeClassName,
|
||||
namespaceIDClassName,
|
||||
attribute.get("id").getAsString(),
|
||||
attribute.get("clientSync").getAsBoolean(),
|
||||
attribute.get("defaultValue").getAsDouble()
|
||||
).addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).build()
|
||||
);
|
||||
} else {
|
||||
// ClampedAttribute
|
||||
attributeClass.addField(
|
||||
FieldSpec.builder(
|
||||
attributeClassName,
|
||||
attributeName
|
||||
).initializer(
|
||||
"new $T($T.from($S), $L, $L, $L, $L)",
|
||||
clampedAttributeClassName,
|
||||
namespaceIDClassName,
|
||||
attribute.get("id").getAsString(),
|
||||
attribute.get("clientSync").getAsBoolean(),
|
||||
attribute.get("defaultValue").getAsDouble(),
|
||||
range.get("minValue").getAsDouble(),
|
||||
range.get("maxValue").getAsDouble()
|
||||
).addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).build()
|
||||
);
|
||||
}
|
||||
// Add to static init.
|
||||
staticBlock.addStatement("$T.ATTRIBUTE_REGISTRY.register($N)", registryClassName, attributeName);
|
||||
}
|
||||
attributeClass.addStaticBlock(staticBlock.build());
|
||||
|
||||
filesToWrite.add(
|
||||
JavaFile.builder("net.minestom.server.attribute", attributeClass.build())
|
||||
.indent(" ")
|
||||
.skipJavaLangImports(true)
|
||||
.build()
|
||||
);
|
||||
filesToWrite.add(
|
||||
JavaFile.builder("net.minestom.server.attribute", clampedAttributeClass.build())
|
||||
.indent(" ")
|
||||
.skipJavaLangImports(true)
|
||||
.build()
|
||||
);
|
||||
|
||||
// Write files to outputFolder
|
||||
writeFiles(
|
||||
filesToWrite,
|
||||
outputFolder
|
||||
);
|
||||
}
|
||||
}
|
|
@ -49,9 +49,20 @@ public class DyeColorGenerator extends MinestomCodeGenerator {
|
|||
.addSuperinterface(ClassName.get("net.kyori.adventure.util", "RGBLike"))
|
||||
.addModifiers(Modifier.PUBLIC).addJavadoc("AUTOGENERATED by " + getClass().getSimpleName());
|
||||
|
||||
ClassName networkBufferCN = ClassName.get("net.minestom.server.network", "NetworkBuffer");
|
||||
ParameterizedTypeName networkBufferTypeCN = ParameterizedTypeName.get(networkBufferCN.nestedClass("Type"), dyeColorCN);
|
||||
ClassName binaryTagSerializerCN = ClassName.get("net.minestom.server.utils.nbt", "BinaryTagSerializer");
|
||||
ParameterizedTypeName binaryTagSerializerTypeCN = ParameterizedTypeName.get(binaryTagSerializerCN, dyeColorCN);
|
||||
|
||||
// Fields
|
||||
dyeColorEnum.addFields(
|
||||
List.of(
|
||||
FieldSpec.builder(networkBufferTypeCN, "NETWORK_TYPE", Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
|
||||
.initializer("$T.fromEnum($T.class)", networkBufferCN, dyeColorCN)
|
||||
.build(),
|
||||
FieldSpec.builder(binaryTagSerializerTypeCN, "NBT_TYPE", Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
|
||||
.initializer("$T.fromEnumStringable($T.class)", binaryTagSerializerCN, dyeColorCN)
|
||||
.build(),
|
||||
FieldSpec.builder(colorCN, "textureDiffuseColor", Modifier.PRIVATE, Modifier.FINAL).build(),
|
||||
FieldSpec.builder(colorCN, "textColor", Modifier.PRIVATE, Modifier.FINAL).build(),
|
||||
FieldSpec.builder(colorCN, "fireworkColor", Modifier.PRIVATE, Modifier.FINAL).build(),
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
package net.minestom.codegen.recipe;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.squareup.javapoet.*;
|
||||
import net.minestom.codegen.MinestomCodeGenerator;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
public class RecipeTypeGenerator extends MinestomCodeGenerator {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(RecipeTypeGenerator.class);
|
||||
private final InputStream recipeTypesFile;
|
||||
private final File outputFolder;
|
||||
|
||||
public RecipeTypeGenerator(@Nullable InputStream recipeTypesFile, @NotNull File outputFolder) {
|
||||
this.recipeTypesFile = recipeTypesFile;
|
||||
this.outputFolder = outputFolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate() {
|
||||
if (recipeTypesFile == null) {
|
||||
LOGGER.error("Failed to find recipe_types.json.");
|
||||
LOGGER.error("Stopped code generation for recipe types.");
|
||||
return;
|
||||
}
|
||||
if (!outputFolder.exists() && !outputFolder.mkdirs()) {
|
||||
LOGGER.error("Output folder for code generation does not exist and could not be created.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Important classes we use alot
|
||||
JsonArray recipeTypes = GSON.fromJson(new InputStreamReader(recipeTypesFile), JsonArray.class);
|
||||
ClassName recipeTypeCN = ClassName.get("net.minestom.server.recipe", "RecipeType");
|
||||
TypeSpec.Builder recipeTypeEnum = TypeSpec.enumBuilder(recipeTypeCN)
|
||||
.addSuperinterface(ClassName.get("net.minestom.server.registry", "StaticProtocolObject"))
|
||||
.addModifiers(Modifier.PUBLIC).addJavadoc("AUTOGENERATED by " + getClass().getSimpleName());
|
||||
ClassName namespaceIdCN = ClassName.get("net.minestom.server.utils", "NamespaceID");
|
||||
|
||||
ClassName networkBufferCN = ClassName.get("net.minestom.server.network", "NetworkBuffer");
|
||||
ParameterizedTypeName networkBufferTypeCN = ParameterizedTypeName.get(networkBufferCN.nestedClass("Type"), recipeTypeCN);
|
||||
|
||||
// Fields
|
||||
recipeTypeEnum.addFields(
|
||||
List.of(
|
||||
FieldSpec.builder(networkBufferTypeCN, "NETWORK_TYPE", Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
|
||||
.initializer("$T.fromEnum($T.class)", networkBufferCN, recipeTypeCN)
|
||||
.build(),
|
||||
FieldSpec.builder(namespaceIdCN, "namespace", Modifier.PRIVATE, Modifier.FINAL).build()
|
||||
)
|
||||
);
|
||||
|
||||
// Methods
|
||||
recipeTypeEnum.addMethods(
|
||||
List.of(
|
||||
// Constructor
|
||||
MethodSpec.constructorBuilder()
|
||||
.addParameter(ParameterSpec.builder(namespaceIdCN, "namespace").addAnnotation(NotNull.class).build())
|
||||
.addStatement("this.namespace = namespace")
|
||||
.build(),
|
||||
MethodSpec.methodBuilder("namespace")
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addAnnotation(NotNull.class)
|
||||
.addAnnotation(Override.class)
|
||||
.returns(namespaceIdCN)
|
||||
.addStatement("return this.namespace")
|
||||
.build(),
|
||||
MethodSpec.methodBuilder("id")
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.returns(TypeName.INT)
|
||||
.addAnnotation(Override.class)
|
||||
.addStatement("return this.ordinal()")
|
||||
.build()
|
||||
)
|
||||
);
|
||||
|
||||
// Use data
|
||||
for (JsonObject recipeTypeObject : StreamSupport.stream(recipeTypes.spliterator(), true).map(JsonElement::getAsJsonObject).sorted(Comparator.comparingInt(o -> o.get("id").getAsInt())).toList()) {
|
||||
String recipeTypeName = recipeTypeObject.get("name").getAsString();
|
||||
recipeTypeEnum.addEnumConstant(recipeTypeConstantName(recipeTypeName), TypeSpec.anonymousClassBuilder(
|
||||
"$T.from($S)",
|
||||
namespaceIdCN, recipeTypeName
|
||||
).build()
|
||||
);
|
||||
}
|
||||
|
||||
// Write files to outputFolder
|
||||
writeFiles(
|
||||
List.of(
|
||||
JavaFile.builder("net.minestom.server.recipe", recipeTypeEnum.build())
|
||||
.indent(" ")
|
||||
.skipJavaLangImports(true)
|
||||
.build()
|
||||
),
|
||||
outputFolder
|
||||
);
|
||||
}
|
||||
|
||||
private static @NotNull String recipeTypeConstantName(@NotNull String name) {
|
||||
return toConstant(name).replace("CRAFTING_", "");
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@ import net.minestom.server.event.server.ServerListPingEvent;
|
|||
import net.minestom.server.extras.lan.OpenToLAN;
|
||||
import net.minestom.server.extras.lan.OpenToLANConfig;
|
||||
import net.minestom.server.instance.block.BlockManager;
|
||||
import net.minestom.server.item.ItemComponent;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.item.Material;
|
||||
import net.minestom.server.network.packet.server.play.DeclareRecipesPacket;
|
||||
|
@ -31,6 +32,11 @@ import java.util.List;
|
|||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
Class.forName(ItemComponent.class.getName());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
System.setProperty("minestom.experiment.pose-updates", "true");
|
||||
|
||||
MinecraftServer.setCompressionThreshold(0);
|
||||
|
@ -76,6 +82,8 @@ public class Main {
|
|||
commandManager.register(new RelightCommand());
|
||||
commandManager.register(new KillCommand());
|
||||
commandManager.register(new WeatherCommand());
|
||||
commandManager.register(new PotionCommand());
|
||||
commandManager.register(new CookieCommand());
|
||||
|
||||
commandManager.setUnknownCommandCallback((sender, command) -> sender.sendMessage(Component.text("Unknown command", NamedTextColor.RED)));
|
||||
|
||||
|
@ -132,6 +140,22 @@ public class Main {
|
|||
}
|
||||
};
|
||||
MinecraftServer.getRecipeManager().addRecipe(ironBlockRecipe);
|
||||
// var recipe = new ShapelessRecipe(
|
||||
// "minestom:test2", "abc",
|
||||
// RecipeCategory.Crafting.MISC,
|
||||
// List.of(
|
||||
// new DeclareRecipesPacket.Ingredient(List.of(ItemStack.AIR))
|
||||
// ),
|
||||
// ItemStack.builder(Material.GOLD_BLOCK)
|
||||
// .set(ItemComponent.CUSTOM_NAME, Component.text("abc"))
|
||||
// .build()
|
||||
// ) {
|
||||
// @Override
|
||||
// public boolean shouldShow(@NotNull Player player) {
|
||||
// return true;
|
||||
// }
|
||||
// };
|
||||
// MinecraftServer.getRecipeManager().addRecipe(recipe);
|
||||
|
||||
PlayerInit.init();
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import net.minestom.server.advancements.notifications.Notification;
|
|||
import net.minestom.server.advancements.notifications.NotificationCenter;
|
||||
import net.minestom.server.adventure.MinestomAdventure;
|
||||
import net.minestom.server.adventure.audience.Audiences;
|
||||
import net.minestom.server.coordinate.Point;
|
||||
import net.minestom.server.coordinate.Pos;
|
||||
import net.minestom.server.coordinate.Vec;
|
||||
import net.minestom.server.entity.Entity;
|
||||
|
@ -26,26 +27,38 @@ import net.minestom.server.instance.InstanceContainer;
|
|||
import net.minestom.server.instance.InstanceManager;
|
||||
import net.minestom.server.instance.LightingChunk;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.inventory.Inventory;
|
||||
import net.minestom.server.instance.block.predicate.BlockPredicate;
|
||||
import net.minestom.server.instance.block.predicate.BlockTypeFilter;
|
||||
import net.minestom.server.inventory.ContainerInventory;
|
||||
import net.minestom.server.inventory.InventoryType;
|
||||
import net.minestom.server.item.ItemComponent;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.item.Material;
|
||||
import net.minestom.server.item.metadata.BundleMeta;
|
||||
import net.minestom.server.item.component.BlockPredicates;
|
||||
import net.minestom.server.item.component.ItemBlockState;
|
||||
import net.minestom.server.monitoring.BenchmarkManager;
|
||||
import net.minestom.server.monitoring.TickMonitor;
|
||||
import net.minestom.server.network.NetworkBuffer;
|
||||
import net.minestom.server.network.packet.server.play.ExplosionPacket;
|
||||
import net.minestom.server.particle.Particle;
|
||||
import net.minestom.server.particle.data.BlockParticleData;
|
||||
import net.minestom.server.sound.SoundEvent;
|
||||
import net.minestom.server.utils.MathUtils;
|
||||
import net.minestom.server.utils.NamespaceID;
|
||||
import net.minestom.server.utils.time.TimeUnit;
|
||||
import net.minestom.server.world.DimensionType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public class PlayerInit {
|
||||
|
||||
private static final Inventory inventory;
|
||||
private static final ContainerInventory inventory;
|
||||
|
||||
private static final EventNode<Event> DEMO_NODE = EventNode.all("demo")
|
||||
.addListener(EntityAttackEvent.class, event -> {
|
||||
|
@ -82,6 +95,8 @@ public class PlayerInit {
|
|||
itemEntity.setInstance(player.getInstance(), playerPos.withY(y -> y + 1.5));
|
||||
Vec velocity = playerPos.direction().mul(6);
|
||||
itemEntity.setVelocity(velocity);
|
||||
|
||||
player.sendPacket(makeExplosion(playerPos, velocity));
|
||||
})
|
||||
.addListener(PlayerDisconnectEvent.class, event -> System.out.println("DISCONNECTION " + event.getPlayer().getUsername()))
|
||||
.addListener(AsyncPlayerConfigurationEvent.class, event -> {
|
||||
|
@ -100,20 +115,26 @@ public class PlayerInit {
|
|||
player.setPermissionLevel(4);
|
||||
ItemStack itemStack = ItemStack.builder(Material.STONE)
|
||||
.amount(64)
|
||||
.meta(itemMetaBuilder ->
|
||||
itemMetaBuilder.canPlaceOn(Set.of(Block.STONE))
|
||||
.canDestroy(Set.of(Block.DIAMOND_ORE)))
|
||||
.set(ItemComponent.CAN_PLACE_ON, new BlockPredicates(new BlockPredicate(new BlockTypeFilter.Blocks(Block.STONE), null, null)))
|
||||
.set(ItemComponent.CAN_BREAK, new BlockPredicates(new BlockPredicate(new BlockTypeFilter.Blocks(Block.DIAMOND_ORE), null, null)))
|
||||
.build();
|
||||
player.getInventory().addItemStack(itemStack);
|
||||
|
||||
ItemStack bundle = ItemStack.builder(Material.BUNDLE)
|
||||
.meta(BundleMeta.class, bundleMetaBuilder -> {
|
||||
bundleMetaBuilder.addItem(ItemStack.of(Material.DIAMOND, 5));
|
||||
bundleMetaBuilder.addItem(ItemStack.of(Material.RABBIT_FOOT, 5));
|
||||
})
|
||||
.set(ItemComponent.BUNDLE_CONTENTS, List.of(
|
||||
ItemStack.of(Material.DIAMOND, 5),
|
||||
ItemStack.of(Material.RABBIT_FOOT, 5)
|
||||
))
|
||||
.build();
|
||||
player.getInventory().addItemStack(bundle);
|
||||
|
||||
player.getInventory().addItemStack(ItemStack.builder(Material.STONE_STAIRS)
|
||||
.set(ItemComponent.BLOCK_STATE, new ItemBlockState(Map.of("facing", "west", "half", "top")))
|
||||
.build());
|
||||
|
||||
player.getInventory().addItemStack(ItemStack.builder(Material.BLACK_BANNER)
|
||||
.build());
|
||||
|
||||
if (event.isFirstSpawn()) {
|
||||
Notification notification = new Notification(
|
||||
Component.text("Welcome!"),
|
||||
|
@ -127,6 +148,7 @@ public class PlayerInit {
|
|||
//System.out.println("out " + event.getPacket().getClass().getSimpleName());
|
||||
})
|
||||
.addListener(PlayerPacketEvent.class, event -> {
|
||||
|
||||
//System.out.println("in " + event.getPacket().getClass().getSimpleName());
|
||||
})
|
||||
.addListener(PlayerUseItemOnBlockEvent.class, event -> {
|
||||
|
@ -156,6 +178,20 @@ public class PlayerInit {
|
|||
event.getInstance().setBlock(event.getBlockPosition(), block);
|
||||
});
|
||||
|
||||
private static final byte[] AIR_BLOCK_PARTICLE = NetworkBuffer.makeArray(new BlockParticleData(Block.AIR)::write);
|
||||
|
||||
private static @NotNull ExplosionPacket makeExplosion(@NotNull Point position, @NotNull Vec motion) {
|
||||
return new ExplosionPacket(
|
||||
position.x(), position.y(), position.z(),
|
||||
0, new byte[0],
|
||||
(float) motion.x(), (float) motion.y(), (float) motion.z(),
|
||||
ExplosionPacket.BlockInteraction.KEEP,
|
||||
Particle.BLOCK.id(), AIR_BLOCK_PARTICLE,
|
||||
Particle.BLOCK.id(), AIR_BLOCK_PARTICLE,
|
||||
SoundEvent.of(NamespaceID.from("not.a.real.sound"), 0f)
|
||||
);
|
||||
}
|
||||
|
||||
static {
|
||||
InstanceManager instanceManager = MinecraftServer.getInstanceManager();
|
||||
|
||||
|
@ -187,7 +223,7 @@ public class PlayerInit {
|
|||
// System.out.println("light end");
|
||||
// });
|
||||
|
||||
inventory = new Inventory(InventoryType.CHEST_1_ROW, Component.text("Test inventory"));
|
||||
inventory = new ContainerInventory(InventoryType.CHEST_1_ROW, Component.text("Test inventory"));
|
||||
inventory.setItemStack(3, ItemStack.of(Material.DIAMOND, 34));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
package net.minestom.demo.block;
|
||||
|
||||
import net.kyori.adventure.nbt.BinaryTag;
|
||||
import net.kyori.adventure.nbt.BinaryTagTypes;
|
||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||
import net.kyori.adventure.nbt.ListBinaryTag;
|
||||
import net.minestom.server.instance.block.BlockHandler;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.item.Material;
|
||||
|
@ -10,10 +14,6 @@ import net.minestom.server.tag.TagWritable;
|
|||
import net.minestom.server.utils.NamespaceID;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBT;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTList;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
@ -22,16 +22,17 @@ import java.util.List;
|
|||
public class CampfireHandler implements BlockHandler {
|
||||
|
||||
public static final Tag<List<ItemStack>> ITEMS = Tag.View(new TagSerializer<>() {
|
||||
private final Tag<NBT> internal = Tag.NBT("Items");
|
||||
private final Tag<BinaryTag> internal = Tag.NBT("Items");
|
||||
|
||||
@Override
|
||||
public @Nullable List<ItemStack> read(@NotNull TagReadable reader) {
|
||||
NBTList<NBTCompound> item = (NBTList<NBTCompound>) reader.getTag(internal);
|
||||
ListBinaryTag item = (ListBinaryTag) reader.getTag(internal);
|
||||
if (item == null)
|
||||
return null;
|
||||
List<ItemStack> result = new ArrayList<>();
|
||||
item.forEach(nbtCompound -> {
|
||||
int amount = nbtCompound.getAsByte("Count");
|
||||
item.forEach(childTag -> {
|
||||
CompoundBinaryTag nbtCompound = (CompoundBinaryTag) childTag;
|
||||
int amount = nbtCompound.getByte("Count");
|
||||
String id = nbtCompound.getString("id");
|
||||
Material material = Material.fromNamespaceId(id);
|
||||
result.add(ItemStack.of(material, amount));
|
||||
|
@ -45,14 +46,14 @@ public class CampfireHandler implements BlockHandler {
|
|||
writer.removeTag(internal);
|
||||
return;
|
||||
}
|
||||
writer.setTag(internal, NBT.List(
|
||||
NBTType.TAG_Compound,
|
||||
writer.setTag(internal, ListBinaryTag.listBinaryTag(
|
||||
BinaryTagTypes.COMPOUND,
|
||||
value.stream()
|
||||
.map(item -> NBT.Compound(nbt -> {
|
||||
nbt.setByte("Count", (byte) item.amount());
|
||||
nbt.setByte("Slot", (byte) 1);
|
||||
nbt.setString("id", item.material().name());
|
||||
}))
|
||||
.map(item -> (BinaryTag) CompoundBinaryTag.builder()
|
||||
.putByte("Count", (byte) item.amount())
|
||||
.putByte("Slot", (byte) 1)
|
||||
.putString("id", item.material().name())
|
||||
.build())
|
||||
.toList()
|
||||
));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
package net.minestom.demo.commands;
|
||||
|
||||
import net.minestom.server.command.CommandSender;
|
||||
import net.minestom.server.command.builder.Command;
|
||||
import net.minestom.server.command.builder.CommandContext;
|
||||
import net.minestom.server.command.builder.arguments.Argument;
|
||||
import net.minestom.server.command.builder.arguments.ArgumentType;
|
||||
import net.minestom.server.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class CookieCommand extends Command {
|
||||
public CookieCommand() {
|
||||
super("cookie");
|
||||
|
||||
addSubcommand(new Store());
|
||||
addSubcommand(new Fetch());
|
||||
}
|
||||
|
||||
public static class Store extends Command {
|
||||
private final Argument<String> keyArg = ArgumentType.ResourceLocation("key");
|
||||
private final Argument<String[]> valueArg = ArgumentType.StringArray("value");
|
||||
|
||||
public Store() {
|
||||
super("store");
|
||||
|
||||
addSyntax(this::store, keyArg, valueArg);
|
||||
}
|
||||
|
||||
private void store(@NotNull CommandSender sender, @NotNull CommandContext context) {
|
||||
if (!(sender instanceof Player player)) return;
|
||||
|
||||
String key = context.get(keyArg);
|
||||
byte[] value = String.join(" ", context.get(valueArg)).getBytes();
|
||||
|
||||
player.getPlayerConnection().storeCookie(key, value);
|
||||
player.sendMessage(key + " stored");
|
||||
}
|
||||
}
|
||||
|
||||
public static class Fetch extends Command {
|
||||
private final Argument<String> keyArg = ArgumentType.ResourceLocation("key");
|
||||
|
||||
public Fetch() {
|
||||
super("fetch");
|
||||
|
||||
addSyntax(this::fetch, keyArg);
|
||||
}
|
||||
|
||||
private void fetch(@NotNull CommandSender sender, @NotNull CommandContext context) {
|
||||
if (!(sender instanceof Player player)) return;
|
||||
|
||||
String key = context.get(keyArg);
|
||||
|
||||
player.getPlayerConnection().fetchCookie(key).thenAccept(value -> {
|
||||
if (value == null) {
|
||||
player.sendMessage(key + ": null");
|
||||
} else {
|
||||
player.sendMessage(key + ": " + new String(value));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,10 +4,10 @@ import net.kyori.adventure.text.Component;
|
|||
import net.minestom.server.command.builder.Command;
|
||||
import net.minestom.server.entity.Entity;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.inventory.PlayerInventory;
|
||||
import net.minestom.server.inventory.TransactionOption;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.utils.entity.EntityFinder;
|
||||
import net.minestom.server.utils.inventory.PlayerInventoryUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -25,7 +25,7 @@ public class GiveCommand extends Command {
|
|||
addSyntax((sender, context) -> {
|
||||
final EntityFinder entityFinder = context.get("target");
|
||||
int count = context.get("count");
|
||||
count = Math.min(count, PlayerInventory.INVENTORY_SIZE * 64);
|
||||
count = Math.min(count, PlayerInventoryUtils.INVENTORY_SIZE * 64);
|
||||
ItemStack itemStack = context.get("item");
|
||||
|
||||
List<ItemStack> itemStacks;
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
package net.minestom.demo.commands;
|
||||
|
||||
import net.minestom.server.command.CommandSender;
|
||||
import net.minestom.server.command.builder.Command;
|
||||
import net.minestom.server.command.builder.CommandContext;
|
||||
import net.minestom.server.command.builder.arguments.Argument;
|
||||
import net.minestom.server.command.builder.arguments.ArgumentType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class PotionCommand extends Command {
|
||||
private final Argument<String> potionArg = ArgumentType.Resource("potion", "minecraft:potion");
|
||||
|
||||
public PotionCommand() {
|
||||
super("potion");
|
||||
|
||||
addSyntax(this::potionCommand, potionArg);
|
||||
}
|
||||
|
||||
private void potionCommand(@NotNull CommandSender sender, @NotNull CommandContext context) {
|
||||
final String potion = context.get(potionArg);
|
||||
sender.sendMessage("Potion: " + potion);
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
package net.minestom.demo.entity;
|
||||
|
||||
import net.minestom.server.attribute.Attribute;
|
||||
import net.minestom.server.entity.EntityCreature;
|
||||
import net.minestom.server.entity.EntityType;
|
||||
import net.minestom.server.entity.ai.goal.RandomStrollGoal;
|
||||
import net.minestom.server.entity.attribute.Attribute;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -35,7 +35,7 @@ public class ChickenCreature extends EntityCreature {
|
|||
// .build()
|
||||
// );
|
||||
|
||||
getAttribute(Attribute.MOVEMENT_SPEED).setBaseValue(0.1f);
|
||||
getAttribute(Attribute.GENERIC_MOVEMENT_SPEED).setBaseValue(0.1);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -3,8 +3,8 @@ metadata.format.version = "1.1"
|
|||
[versions]
|
||||
|
||||
# Important dependencies
|
||||
data = "1.20.4-rv4"
|
||||
adventure = "4.15.0"
|
||||
data = "1.20.5-rv1"
|
||||
adventure = "4.16.0"
|
||||
kotlin = "1.7.22"
|
||||
dependencyGetter = "v1.0.1"
|
||||
hydrazine = "1.7.2"
|
||||
|
@ -41,22 +41,16 @@ nexuspublish = "1.3.0"
|
|||
# Important Dependencies
|
||||
# Adventure
|
||||
adventure-api = { group = "net.kyori", name = "adventure-api", version.ref = "adventure" }
|
||||
adventure-nbt = { group = "net.kyori", name = "adventure-nbt", version.ref = "adventure" }
|
||||
adventure-serializer-gson = { group = "net.kyori", name = "adventure-text-serializer-gson", version.ref = "adventure" }
|
||||
adventure-serializer-legacy = { group = "net.kyori", name = "adventure-text-serializer-legacy", version.ref = "adventure" }
|
||||
adventure-serializer-plain = { group = "net.kyori", name = "adventure-text-serializer-plain", version.ref = "adventure" }
|
||||
adventure-text-logger-slf4j = { group = "net.kyori", name = "adventure-text-logger-slf4j", version.ref = "adventure" }
|
||||
|
||||
# Kotlin
|
||||
kotlin-reflect = { group = "org.jetbrains.kotlin", name = "kotlin-reflect", version.ref = "kotlin" }
|
||||
kotlin-stdlib-jdk8 = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib-jdk8", version.ref = "kotlin" }
|
||||
|
||||
# Miscellaneous
|
||||
hydrazine = { group = "com.github.MadMartian", name = "hydrazine-path-finding", version.ref = "hydrazine" }
|
||||
minestomData = { group = "net.minestom", name = "data", version.ref = "data" }
|
||||
dependencyGetter = { group = "com.github.Minestom", name = "DependencyGetter", version.ref = "dependencyGetter" }
|
||||
jetbrainsAnnotations = { group = "org.jetbrains", name = "annotations", version.ref = "jetbrainsAnnotations" }
|
||||
hephaistos-common = { group = "io.github.jglrxavpok.hephaistos", name = "common", version.ref = "hephaistos" }
|
||||
hephaistos-gson = { group = "io.github.jglrxavpok.hephaistos", name = "gson", version.ref = "hephaistos" }
|
||||
slf4j = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j"}
|
||||
|
||||
# Performance / Data Structures
|
||||
|
@ -86,11 +80,9 @@ logback-classic = { group = "ch.qos.logback", name = "logback-classic", version.
|
|||
|
||||
[bundles]
|
||||
|
||||
kotlin = ["kotlin-stdlib-jdk8", "kotlin-reflect"]
|
||||
flare = ["flare", "flare-fastutil"]
|
||||
adventure = ["adventure-api", "adventure-serializer-gson", "adventure-serializer-legacy", "adventure-serializer-plain", "adventure-text-logger-slf4j"]
|
||||
adventure = ["adventure-api", "adventure-nbt", "adventure-serializer-gson", "adventure-serializer-legacy", "adventure-serializer-plain", "adventure-text-logger-slf4j"]
|
||||
junit = ["junit-api", "junit-engine", "junit-params", "junit-suite-api", "junit-suite-engine"]
|
||||
hephaistos = ["hephaistos-common", "hephaistos-gson"]
|
||||
logback = ["logback-core", "logback-classic"]
|
||||
|
||||
[plugins]
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package net.minestom.server.color;
|
||||
|
||||
import net.kyori.adventure.util.RGBLike;
|
||||
import net.minestom.server.network.NetworkBuffer;
|
||||
import net.minestom.server.utils.nbt.BinaryTagSerializer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
|
@ -39,6 +41,10 @@ public enum DyeColor implements RGBLike {
|
|||
|
||||
BLACK(new Color(0x1d1d21), new Color(0x0), new Color(0x1e1b1b), 29);
|
||||
|
||||
public static final NetworkBuffer.Type<DyeColor> NETWORK_TYPE = NetworkBuffer.fromEnum(DyeColor.class);
|
||||
|
||||
public static final BinaryTagSerializer<DyeColor> NBT_TYPE = BinaryTagSerializer.fromEnumStringable(DyeColor.class);
|
||||
|
||||
private final Color textureDiffuseColor;
|
||||
|
||||
private final Color textColor;
|
||||
|
|
|
@ -9,6 +9,8 @@ interface EntityTypes {
|
|||
|
||||
EntityType AREA_EFFECT_CLOUD = EntityTypeImpl.get("minecraft:area_effect_cloud");
|
||||
|
||||
EntityType ARMADILLO = EntityTypeImpl.get("minecraft:armadillo");
|
||||
|
||||
EntityType ARMOR_STAND = EntityTypeImpl.get("minecraft:armor_stand");
|
||||
|
||||
EntityType ARROW = EntityTypeImpl.get("minecraft:arrow");
|
||||
|
@ -25,8 +27,12 @@ interface EntityTypes {
|
|||
|
||||
EntityType BOAT = EntityTypeImpl.get("minecraft:boat");
|
||||
|
||||
EntityType BOGGED = EntityTypeImpl.get("minecraft:bogged");
|
||||
|
||||
EntityType BREEZE = EntityTypeImpl.get("minecraft:breeze");
|
||||
|
||||
EntityType BREEZE_WIND_CHARGE = EntityTypeImpl.get("minecraft:breeze_wind_charge");
|
||||
|
||||
EntityType CAMEL = EntityTypeImpl.get("minecraft:camel");
|
||||
|
||||
EntityType CAT = EntityTypeImpl.get("minecraft:cat");
|
||||
|
@ -121,6 +127,8 @@ interface EntityTypes {
|
|||
|
||||
EntityType ITEM_FRAME = EntityTypeImpl.get("minecraft:item_frame");
|
||||
|
||||
EntityType OMINOUS_ITEM_SPAWNER = EntityTypeImpl.get("minecraft:ominous_item_spawner");
|
||||
|
||||
EntityType FIREBALL = EntityTypeImpl.get("minecraft:fireball");
|
||||
|
||||
EntityType LEASH_KNOT = EntityTypeImpl.get("minecraft:leash_knot");
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
package net.minestom.server.entity.attribute;
|
||||
|
||||
/**
|
||||
* Code autogenerated, do not edit!
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
interface Attributes {
|
||||
Attribute GENERIC_ARMOR = AttributeImpl.get("minecraft:generic.armor");
|
||||
|
||||
Attribute GENERIC_ARMOR_TOUGHNESS = AttributeImpl.get("minecraft:generic.armor_toughness");
|
||||
|
||||
Attribute GENERIC_ATTACK_DAMAGE = AttributeImpl.get("minecraft:generic.attack_damage");
|
||||
|
||||
Attribute GENERIC_ATTACK_KNOCKBACK = AttributeImpl.get("minecraft:generic.attack_knockback");
|
||||
|
||||
Attribute GENERIC_ATTACK_SPEED = AttributeImpl.get("minecraft:generic.attack_speed");
|
||||
|
||||
Attribute PLAYER_BLOCK_BREAK_SPEED = AttributeImpl.get("minecraft:player.block_break_speed");
|
||||
|
||||
Attribute PLAYER_BLOCK_INTERACTION_RANGE = AttributeImpl.get("minecraft:player.block_interaction_range");
|
||||
|
||||
Attribute PLAYER_ENTITY_INTERACTION_RANGE = AttributeImpl.get("minecraft:player.entity_interaction_range");
|
||||
|
||||
Attribute GENERIC_FALL_DAMAGE_MULTIPLIER = AttributeImpl.get("minecraft:generic.fall_damage_multiplier");
|
||||
|
||||
Attribute GENERIC_FLYING_SPEED = AttributeImpl.get("minecraft:generic.flying_speed");
|
||||
|
||||
Attribute GENERIC_FOLLOW_RANGE = AttributeImpl.get("minecraft:generic.follow_range");
|
||||
|
||||
Attribute GENERIC_GRAVITY = AttributeImpl.get("minecraft:generic.gravity");
|
||||
|
||||
Attribute GENERIC_JUMP_STRENGTH = AttributeImpl.get("minecraft:generic.jump_strength");
|
||||
|
||||
Attribute GENERIC_KNOCKBACK_RESISTANCE = AttributeImpl.get("minecraft:generic.knockback_resistance");
|
||||
|
||||
Attribute GENERIC_LUCK = AttributeImpl.get("minecraft:generic.luck");
|
||||
|
||||
Attribute GENERIC_MAX_ABSORPTION = AttributeImpl.get("minecraft:generic.max_absorption");
|
||||
|
||||
Attribute GENERIC_MAX_HEALTH = AttributeImpl.get("minecraft:generic.max_health");
|
||||
|
||||
Attribute GENERIC_MOVEMENT_SPEED = AttributeImpl.get("minecraft:generic.movement_speed");
|
||||
|
||||
Attribute GENERIC_SAFE_FALL_DISTANCE = AttributeImpl.get("minecraft:generic.safe_fall_distance");
|
||||
|
||||
Attribute GENERIC_SCALE = AttributeImpl.get("minecraft:generic.scale");
|
||||
|
||||
Attribute ZOMBIE_SPAWN_REINFORCEMENTS = AttributeImpl.get("minecraft:zombie.spawn_reinforcements");
|
||||
|
||||
Attribute GENERIC_STEP_HEIGHT = AttributeImpl.get("minecraft:generic.step_height");
|
||||
}
|
|
@ -33,6 +33,8 @@ interface DamageTypes {
|
|||
|
||||
DamageType PLAYER_EXPLOSION = DamageTypeImpl.get("minecraft:player_explosion");
|
||||
|
||||
DamageType SPIT = DamageTypeImpl.get("minecraft:spit");
|
||||
|
||||
DamageType STING = DamageTypeImpl.get("minecraft:sting");
|
||||
|
||||
DamageType UNATTRIBUTED_FIREBALL = DamageTypeImpl.get("minecraft:unattributed_fireball");
|
||||
|
|
|
@ -2120,4 +2120,8 @@ interface Blocks {
|
|||
Block CRAFTER = BlockImpl.get("minecraft:crafter");
|
||||
|
||||
Block TRIAL_SPAWNER = BlockImpl.get("minecraft:trial_spawner");
|
||||
|
||||
Block VAULT = BlockImpl.get("minecraft:vault");
|
||||
|
||||
Block HEAVY_CORE = BlockImpl.get("minecraft:heavy_core");
|
||||
}
|
||||
|
|
|
@ -175,6 +175,8 @@ interface Materials {
|
|||
|
||||
Material RAW_GOLD_BLOCK = MaterialImpl.get("minecraft:raw_gold_block");
|
||||
|
||||
Material HEAVY_CORE = MaterialImpl.get("minecraft:heavy_core");
|
||||
|
||||
Material AMETHYST_BLOCK = MaterialImpl.get("minecraft:amethyst_block");
|
||||
|
||||
Material BUDDING_AMETHYST = MaterialImpl.get("minecraft:budding_amethyst");
|
||||
|
@ -1593,7 +1595,11 @@ interface Materials {
|
|||
|
||||
Material TURTLE_HELMET = MaterialImpl.get("minecraft:turtle_helmet");
|
||||
|
||||
Material SCUTE = MaterialImpl.get("minecraft:scute");
|
||||
Material TURTLE_SCUTE = MaterialImpl.get("minecraft:turtle_scute");
|
||||
|
||||
Material ARMADILLO_SCUTE = MaterialImpl.get("minecraft:armadillo_scute");
|
||||
|
||||
Material WOLF_ARMOR = MaterialImpl.get("minecraft:wolf_armor");
|
||||
|
||||
Material FLINT_AND_STEEL = MaterialImpl.get("minecraft:flint_and_steel");
|
||||
|
||||
|
@ -2015,6 +2021,8 @@ interface Materials {
|
|||
|
||||
Material GLISTERING_MELON_SLICE = MaterialImpl.get("minecraft:glistering_melon_slice");
|
||||
|
||||
Material ARMADILLO_SPAWN_EGG = MaterialImpl.get("minecraft:armadillo_spawn_egg");
|
||||
|
||||
Material ALLAY_SPAWN_EGG = MaterialImpl.get("minecraft:allay_spawn_egg");
|
||||
|
||||
Material AXOLOTL_SPAWN_EGG = MaterialImpl.get("minecraft:axolotl_spawn_egg");
|
||||
|
@ -2025,6 +2033,8 @@ interface Materials {
|
|||
|
||||
Material BLAZE_SPAWN_EGG = MaterialImpl.get("minecraft:blaze_spawn_egg");
|
||||
|
||||
Material BOGGED_SPAWN_EGG = MaterialImpl.get("minecraft:bogged_spawn_egg");
|
||||
|
||||
Material BREEZE_SPAWN_EGG = MaterialImpl.get("minecraft:breeze_spawn_egg");
|
||||
|
||||
Material CAT_SPAWN_EGG = MaterialImpl.get("minecraft:cat_spawn_egg");
|
||||
|
@ -2175,10 +2185,14 @@ interface Materials {
|
|||
|
||||
Material FIRE_CHARGE = MaterialImpl.get("minecraft:fire_charge");
|
||||
|
||||
Material WIND_CHARGE = MaterialImpl.get("minecraft:wind_charge");
|
||||
|
||||
Material WRITABLE_BOOK = MaterialImpl.get("minecraft:writable_book");
|
||||
|
||||
Material WRITTEN_BOOK = MaterialImpl.get("minecraft:written_book");
|
||||
|
||||
Material MACE = MaterialImpl.get("minecraft:mace");
|
||||
|
||||
Material ITEM_FRAME = MaterialImpl.get("minecraft:item_frame");
|
||||
|
||||
Material GLOW_ITEM_FRAME = MaterialImpl.get("minecraft:glow_item_frame");
|
||||
|
@ -2387,6 +2401,10 @@ interface Materials {
|
|||
|
||||
Material PIGLIN_BANNER_PATTERN = MaterialImpl.get("minecraft:piglin_banner_pattern");
|
||||
|
||||
Material FLOW_BANNER_PATTERN = MaterialImpl.get("minecraft:flow_banner_pattern");
|
||||
|
||||
Material GUSTER_BANNER_PATTERN = MaterialImpl.get("minecraft:guster_banner_pattern");
|
||||
|
||||
Material GOAT_HORN = MaterialImpl.get("minecraft:goat_horn");
|
||||
|
||||
Material COMPOSTER = MaterialImpl.get("minecraft:composter");
|
||||
|
@ -2553,6 +2571,10 @@ interface Materials {
|
|||
|
||||
Material HOST_ARMOR_TRIM_SMITHING_TEMPLATE = MaterialImpl.get("minecraft:host_armor_trim_smithing_template");
|
||||
|
||||
Material FLOW_ARMOR_TRIM_SMITHING_TEMPLATE = MaterialImpl.get("minecraft:flow_armor_trim_smithing_template");
|
||||
|
||||
Material BOLT_ARMOR_TRIM_SMITHING_TEMPLATE = MaterialImpl.get("minecraft:bolt_armor_trim_smithing_template");
|
||||
|
||||
Material ANGLER_POTTERY_SHERD = MaterialImpl.get("minecraft:angler_pottery_sherd");
|
||||
|
||||
Material ARCHER_POTTERY_SHERD = MaterialImpl.get("minecraft:archer_pottery_sherd");
|
||||
|
@ -2569,8 +2591,12 @@ interface Materials {
|
|||
|
||||
Material EXPLORER_POTTERY_SHERD = MaterialImpl.get("minecraft:explorer_pottery_sherd");
|
||||
|
||||
Material FLOW_POTTERY_SHERD = MaterialImpl.get("minecraft:flow_pottery_sherd");
|
||||
|
||||
Material FRIEND_POTTERY_SHERD = MaterialImpl.get("minecraft:friend_pottery_sherd");
|
||||
|
||||
Material GUSTER_POTTERY_SHERD = MaterialImpl.get("minecraft:guster_pottery_sherd");
|
||||
|
||||
Material HEART_POTTERY_SHERD = MaterialImpl.get("minecraft:heart_pottery_sherd");
|
||||
|
||||
Material HEARTBREAK_POTTERY_SHERD = MaterialImpl.get("minecraft:heartbreak_pottery_sherd");
|
||||
|
@ -2585,6 +2611,8 @@ interface Materials {
|
|||
|
||||
Material PRIZE_POTTERY_SHERD = MaterialImpl.get("minecraft:prize_pottery_sherd");
|
||||
|
||||
Material SCRAPE_POTTERY_SHERD = MaterialImpl.get("minecraft:scrape_pottery_sherd");
|
||||
|
||||
Material SHEAF_POTTERY_SHERD = MaterialImpl.get("minecraft:sheaf_pottery_sherd");
|
||||
|
||||
Material SHELTER_POTTERY_SHERD = MaterialImpl.get("minecraft:shelter_pottery_sherd");
|
||||
|
@ -2628,4 +2656,12 @@ interface Materials {
|
|||
Material TRIAL_SPAWNER = MaterialImpl.get("minecraft:trial_spawner");
|
||||
|
||||
Material TRIAL_KEY = MaterialImpl.get("minecraft:trial_key");
|
||||
|
||||
Material OMINOUS_TRIAL_KEY = MaterialImpl.get("minecraft:ominous_trial_key");
|
||||
|
||||
Material VAULT = MaterialImpl.get("minecraft:vault");
|
||||
|
||||
Material OMINOUS_BOTTLE = MaterialImpl.get("minecraft:ominous_bottle");
|
||||
|
||||
Material BREEZE_ROD = MaterialImpl.get("minecraft:breeze_rod");
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package net.minestom.server.item;
|
||||
package net.minestom.server.item.enchant;
|
||||
|
||||
/**
|
||||
* Code autogenerated, do not edit!
|
||||
|
@ -43,7 +43,7 @@ interface Enchantments {
|
|||
|
||||
Enchantment LOOTING = EnchantmentImpl.get("minecraft:looting");
|
||||
|
||||
Enchantment SWEEPING = EnchantmentImpl.get("minecraft:sweeping");
|
||||
Enchantment SWEEPING_EDGE = EnchantmentImpl.get("minecraft:sweeping_edge");
|
||||
|
||||
Enchantment EFFICIENCY = EnchantmentImpl.get("minecraft:efficiency");
|
||||
|
||||
|
@ -79,6 +79,12 @@ interface Enchantments {
|
|||
|
||||
Enchantment PIERCING = EnchantmentImpl.get("minecraft:piercing");
|
||||
|
||||
Enchantment DENSITY = EnchantmentImpl.get("minecraft:density");
|
||||
|
||||
Enchantment BREACH = EnchantmentImpl.get("minecraft:breach");
|
||||
|
||||
Enchantment WIND_BURST = EnchantmentImpl.get("minecraft:wind_burst");
|
||||
|
||||
Enchantment MENDING = EnchantmentImpl.get("minecraft:mending");
|
||||
|
||||
Enchantment VANISHING_CURSE = EnchantmentImpl.get("minecraft:vanishing_curse");
|
|
@ -5,8 +5,6 @@ package net.minestom.server.particle;
|
|||
*/
|
||||
@SuppressWarnings("unused")
|
||||
interface Particles {
|
||||
Particle AMBIENT_ENTITY_EFFECT = ParticleImpl.get("minecraft:ambient_entity_effect");
|
||||
|
||||
Particle ANGRY_VILLAGER = ParticleImpl.get("minecraft:angry_villager");
|
||||
|
||||
Particle BLOCK = ParticleImpl.get("minecraft:block");
|
||||
|
@ -55,7 +53,11 @@ interface Particles {
|
|||
|
||||
Particle GUST = ParticleImpl.get("minecraft:gust");
|
||||
|
||||
Particle GUST_EMITTER = ParticleImpl.get("minecraft:gust_emitter");
|
||||
Particle SMALL_GUST = ParticleImpl.get("minecraft:small_gust");
|
||||
|
||||
Particle GUST_EMITTER_LARGE = ParticleImpl.get("minecraft:gust_emitter_large");
|
||||
|
||||
Particle GUST_EMITTER_SMALL = ParticleImpl.get("minecraft:gust_emitter_small");
|
||||
|
||||
Particle SONIC_BOOM = ParticleImpl.get("minecraft:sonic_boom");
|
||||
|
||||
|
@ -67,6 +69,8 @@ interface Particles {
|
|||
|
||||
Particle FLAME = ParticleImpl.get("minecraft:flame");
|
||||
|
||||
Particle INFESTED = ParticleImpl.get("minecraft:infested");
|
||||
|
||||
Particle CHERRY_LEAVES = ParticleImpl.get("minecraft:cherry_leaves");
|
||||
|
||||
Particle SCULK_SOUL = ParticleImpl.get("minecraft:sculk_soul");
|
||||
|
@ -95,6 +99,8 @@ interface Particles {
|
|||
|
||||
Particle ITEM_SLIME = ParticleImpl.get("minecraft:item_slime");
|
||||
|
||||
Particle ITEM_COBWEB = ParticleImpl.get("minecraft:item_cobweb");
|
||||
|
||||
Particle ITEM_SNOWBALL = ParticleImpl.get("minecraft:item_snowball");
|
||||
|
||||
Particle LARGE_SMOKE = ParticleImpl.get("minecraft:large_smoke");
|
||||
|
@ -203,7 +209,17 @@ interface Particles {
|
|||
|
||||
Particle DUST_PLUME = ParticleImpl.get("minecraft:dust_plume");
|
||||
|
||||
Particle GUST_DUST = ParticleImpl.get("minecraft:gust_dust");
|
||||
|
||||
Particle TRIAL_SPAWNER_DETECTION = ParticleImpl.get("minecraft:trial_spawner_detection");
|
||||
|
||||
Particle TRIAL_SPAWNER_DETECTION_OMINOUS = ParticleImpl.get("minecraft:trial_spawner_detection_ominous");
|
||||
|
||||
Particle VAULT_CONNECTION = ParticleImpl.get("minecraft:vault_connection");
|
||||
|
||||
Particle DUST_PILLAR = ParticleImpl.get("minecraft:dust_pillar");
|
||||
|
||||
Particle OMINOUS_SPAWNING = ParticleImpl.get("minecraft:ominous_spawning");
|
||||
|
||||
Particle RAID_OMEN = ParticleImpl.get("minecraft:raid_omen");
|
||||
|
||||
Particle TRIAL_OMEN = ParticleImpl.get("minecraft:trial_omen");
|
||||
}
|
||||
|
|
|
@ -70,4 +70,16 @@ interface PotionEffects {
|
|||
PotionEffect HERO_OF_THE_VILLAGE = PotionEffectImpl.get("minecraft:hero_of_the_village");
|
||||
|
||||
PotionEffect DARKNESS = PotionEffectImpl.get("minecraft:darkness");
|
||||
|
||||
PotionEffect TRIAL_OMEN = PotionEffectImpl.get("minecraft:trial_omen");
|
||||
|
||||
PotionEffect RAID_OMEN = PotionEffectImpl.get("minecraft:raid_omen");
|
||||
|
||||
PotionEffect WIND_CHARGED = PotionEffectImpl.get("minecraft:wind_charged");
|
||||
|
||||
PotionEffect WEAVING = PotionEffectImpl.get("minecraft:weaving");
|
||||
|
||||
PotionEffect OOZING = PotionEffectImpl.get("minecraft:oozing");
|
||||
|
||||
PotionEffect INFESTED = PotionEffectImpl.get("minecraft:infested");
|
||||
}
|
||||
|
|
|
@ -5,8 +5,6 @@ package net.minestom.server.potion;
|
|||
*/
|
||||
@SuppressWarnings("unused")
|
||||
interface PotionTypes {
|
||||
PotionType EMPTY = PotionTypeImpl.get("minecraft:empty");
|
||||
|
||||
PotionType WATER = PotionTypeImpl.get("minecraft:water");
|
||||
|
||||
PotionType MUNDANE = PotionTypeImpl.get("minecraft:mundane");
|
||||
|
@ -90,4 +88,12 @@ interface PotionTypes {
|
|||
PotionType SLOW_FALLING = PotionTypeImpl.get("minecraft:slow_falling");
|
||||
|
||||
PotionType LONG_SLOW_FALLING = PotionTypeImpl.get("minecraft:long_slow_falling");
|
||||
|
||||
PotionType WIND_CHARGED = PotionTypeImpl.get("minecraft:wind_charged");
|
||||
|
||||
PotionType WEAVING = PotionTypeImpl.get("minecraft:weaving");
|
||||
|
||||
PotionType OOZING = PotionTypeImpl.get("minecraft:oozing");
|
||||
|
||||
PotionType INFESTED = PotionTypeImpl.get("minecraft:infested");
|
||||
}
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
package net.minestom.server.recipe;
|
||||
|
||||
import net.minestom.server.network.NetworkBuffer;
|
||||
import net.minestom.server.registry.StaticProtocolObject;
|
||||
import net.minestom.server.utils.NamespaceID;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* AUTOGENERATED by RecipeTypeGenerator
|
||||
*/
|
||||
public enum RecipeType implements StaticProtocolObject {
|
||||
SHAPED(NamespaceID.from("minecraft:crafting_shaped")),
|
||||
|
||||
SHAPELESS(NamespaceID.from("minecraft:crafting_shapeless")),
|
||||
|
||||
SPECIAL_ARMORDYE(NamespaceID.from("minecraft:crafting_special_armordye")),
|
||||
|
||||
SPECIAL_BOOKCLONING(NamespaceID.from("minecraft:crafting_special_bookcloning")),
|
||||
|
||||
SPECIAL_MAPCLONING(NamespaceID.from("minecraft:crafting_special_mapcloning")),
|
||||
|
||||
SPECIAL_MAPEXTENDING(NamespaceID.from("minecraft:crafting_special_mapextending")),
|
||||
|
||||
SPECIAL_FIREWORK_ROCKET(NamespaceID.from("minecraft:crafting_special_firework_rocket")),
|
||||
|
||||
SPECIAL_FIREWORK_STAR(NamespaceID.from("minecraft:crafting_special_firework_star")),
|
||||
|
||||
SPECIAL_FIREWORK_STAR_FADE(NamespaceID.from("minecraft:crafting_special_firework_star_fade")),
|
||||
|
||||
SPECIAL_TIPPEDARROW(NamespaceID.from("minecraft:crafting_special_tippedarrow")),
|
||||
|
||||
SPECIAL_BANNERDUPLICATE(NamespaceID.from("minecraft:crafting_special_bannerduplicate")),
|
||||
|
||||
SPECIAL_SHIELDDECORATION(NamespaceID.from("minecraft:crafting_special_shielddecoration")),
|
||||
|
||||
SPECIAL_SHULKERBOXCOLORING(NamespaceID.from("minecraft:crafting_special_shulkerboxcoloring")),
|
||||
|
||||
SPECIAL_SUSPICIOUSSTEW(NamespaceID.from("minecraft:crafting_special_suspiciousstew")),
|
||||
|
||||
SPECIAL_REPAIRITEM(NamespaceID.from("minecraft:crafting_special_repairitem")),
|
||||
|
||||
SMELTING(NamespaceID.from("minecraft:smelting")),
|
||||
|
||||
BLASTING(NamespaceID.from("minecraft:blasting")),
|
||||
|
||||
SMOKING(NamespaceID.from("minecraft:smoking")),
|
||||
|
||||
CAMPFIRE_COOKING(NamespaceID.from("minecraft:campfire_cooking")),
|
||||
|
||||
STONECUTTING(NamespaceID.from("minecraft:stonecutting")),
|
||||
|
||||
SMITHING_TRANSFORM(NamespaceID.from("minecraft:smithing_transform")),
|
||||
|
||||
SMITHING_TRIM(NamespaceID.from("minecraft:smithing_trim")),
|
||||
|
||||
DECORATED_POT(NamespaceID.from("minecraft:crafting_decorated_pot"));
|
||||
|
||||
public static final NetworkBuffer.Type<RecipeType> NETWORK_TYPE = NetworkBuffer.fromEnum(RecipeType.class);
|
||||
|
||||
private final NamespaceID namespace;
|
||||
|
||||
RecipeType(@NotNull NamespaceID namespace) {
|
||||
this.namespace = namespace;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public NamespaceID namespace() {
|
||||
return this.namespace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int id() {
|
||||
return this.ordinal();
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,64 @@
|
|||
package net.kyori.adventure.nbt;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
// Based on net.kyori.adventure.nbt.TagStringIO licensed under the MIT license.
|
||||
// https://github.com/KyoriPowered/adventure/blob/main/4/nbt/src/main/java/net/kyori/adventure/nbt/TagStringIO.java
|
||||
public final class TagStringIOExt {
|
||||
|
||||
public static @NotNull String writeTag(@NotNull BinaryTag tag) {
|
||||
return writeTag(tag, "");
|
||||
}
|
||||
|
||||
public static @NotNull String writeTag(@NotNull BinaryTag input, @NotNull String indent) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
try (final TagStringWriter emit = new TagStringWriter(sb, indent)) {
|
||||
emit.writeTag(input);
|
||||
} catch (IOException e) {
|
||||
// The IOException comes from Writer#close(), but we are passing a StringBuilder which
|
||||
// is not a writer and does not need to be closed so will not throw.
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static @NotNull BinaryTag readTag(@NotNull String input) throws IOException {
|
||||
try {
|
||||
final CharBuffer buffer = new CharBuffer(input);
|
||||
final TagStringReader parser = new TagStringReader(buffer);
|
||||
final BinaryTag tag = parser.tag();
|
||||
if (buffer.skipWhitespace().hasMore()) {
|
||||
throw new IOException("Document had trailing content after first tag");
|
||||
}
|
||||
return tag;
|
||||
} catch (final StringTagParseException ex) {
|
||||
throw new IOException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a tag and returns the remainder of the input buffer.
|
||||
*/
|
||||
public static Map.Entry<@NotNull BinaryTag, @NotNull String> readTagEmbedded(@NotNull String input) throws IOException {
|
||||
try {
|
||||
final CharBuffer buffer = new CharBuffer(input);
|
||||
final TagStringReader parser = new TagStringReader(buffer);
|
||||
final BinaryTag tag = parser.tag();
|
||||
|
||||
// Collect remaining (todo figure out a better way, probably need to just write an snbt parser)
|
||||
final StringBuilder remainder = new StringBuilder();
|
||||
while (buffer.hasMore()) {
|
||||
remainder.append(buffer.take());
|
||||
}
|
||||
|
||||
return Map.entry(tag, remainder.toString());
|
||||
} catch (final StringTagParseException ex) {
|
||||
throw new IOException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private TagStringIOExt() {}
|
||||
}
|
|
@ -44,8 +44,9 @@ public final class MinecraftServer {
|
|||
|
||||
public static final ComponentLogger LOGGER = ComponentLogger.logger(MinecraftServer.class);
|
||||
|
||||
public static final String VERSION_NAME = "1.20.4";
|
||||
public static final int PROTOCOL_VERSION = 765;
|
||||
public static final String VERSION_NAME = "1.20.5";
|
||||
public static final int PROTOCOL_VERSION = 766;
|
||||
public static final int DATA_VERSION = 3837;
|
||||
|
||||
// Threads
|
||||
public static final String THREAD_NAME_BENCHMARK = "Ms-Benchmark";
|
||||
|
|
|
@ -42,7 +42,6 @@ public final class ServerFlag {
|
|||
public static final @NotNull String AUTH_URL = System.getProperty("minestom.auth.url", "https://sessionserver.mojang.com/session/minecraft/hasJoined");
|
||||
|
||||
// World
|
||||
public static final @Nullable String STACKING_RULE = System.getProperty("minestom.stacking-rule");
|
||||
public static final int WORLD_BORDER_SIZE = Integer.getInteger("minestom.world-border-size", 29999984);
|
||||
|
||||
// Maps
|
||||
|
|
|
@ -10,11 +10,11 @@ import net.kyori.adventure.text.Component;
|
|||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.title.Title;
|
||||
import net.kyori.adventure.title.TitlePart;
|
||||
import net.minestom.server.coordinate.Vec;
|
||||
import net.minestom.server.entity.Entity;
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import net.minestom.server.network.packet.server.play.*;
|
||||
import net.minestom.server.sound.SoundEvent;
|
||||
import net.minestom.server.utils.NamespaceID;
|
||||
import net.minestom.server.utils.TickUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
@ -111,15 +111,12 @@ public class AdventurePacketConvertor {
|
|||
* @return the sound packet
|
||||
*/
|
||||
public static @NotNull ServerPacket createSoundPacket(@NotNull Sound sound, double x, double y, double z) {
|
||||
final SoundEvent minestomSound = SoundEvent.fromNamespaceId(sound.name().asString());
|
||||
final NamespaceID soundName = NamespaceID.from(sound.name().asString());
|
||||
SoundEvent minestomSound = SoundEvent.fromNamespaceId(soundName);
|
||||
if (minestomSound == null) minestomSound = SoundEvent.of(soundName, null);
|
||||
|
||||
final long seed = sound.seed().orElse(ThreadLocalRandom.current().nextLong());
|
||||
if (minestomSound == null) {
|
||||
return new SoundEffectPacket(sound.name().asString(), null, sound.source(),
|
||||
new Vec(x, y, z), sound.volume(), sound.pitch(), seed);
|
||||
} else {
|
||||
return new SoundEffectPacket(minestomSound, null, sound.source(),
|
||||
new Vec(x, y, z), sound.volume(), sound.pitch(), seed);
|
||||
}
|
||||
return new SoundEffectPacket(minestomSound, sound.source(), (int) x, (int) y, (int) z, sound.volume(), sound.pitch(), seed);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -136,14 +133,12 @@ public class AdventurePacketConvertor {
|
|||
if (!(emitter instanceof Entity entity))
|
||||
throw new IllegalArgumentException("you can only call this method with entities");
|
||||
|
||||
final SoundEvent minestomSound = SoundEvent.fromNamespaceId(sound.name().asString());
|
||||
final long seed = sound.seed().orElse(ThreadLocalRandom.current().nextLong());
|
||||
final NamespaceID soundName = NamespaceID.from(sound.name().asString());
|
||||
SoundEvent minestomSound = SoundEvent.fromNamespaceId(soundName);
|
||||
if (minestomSound == null) minestomSound = SoundEvent.of(soundName, null);
|
||||
|
||||
if (minestomSound != null) {
|
||||
return new EntitySoundEffectPacket(minestomSound, null, sound.source(), entity.getEntityId(), sound.volume(), sound.pitch(), seed);
|
||||
} else {
|
||||
return new EntitySoundEffectPacket(sound.name().asString(), null, sound.source(), entity.getEntityId(), sound.volume(), sound.pitch(), seed);
|
||||
}
|
||||
final long seed = sound.seed().orElse(ThreadLocalRandom.current().nextLong());
|
||||
return new EntitySoundEffectPacket(minestomSound, sound.source(), entity.getEntityId(), sound.volume(), sound.pitch(), seed);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,21 +1,18 @@
|
|||
package net.minestom.server.adventure;
|
||||
|
||||
import java.io.StringReader;
|
||||
|
||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||
import net.kyori.adventure.nbt.TagStringIO;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.translation.GlobalTranslator;
|
||||
import net.kyori.adventure.util.Codec;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import org.jglrxavpok.hephaistos.nbt.NBT;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTException;
|
||||
import org.jglrxavpok.hephaistos.parser.SNBTParser;
|
||||
|
||||
/**
|
||||
* Adventure related constants, etc.
|
||||
*/
|
||||
|
@ -23,8 +20,8 @@ public final class MinestomAdventure {
|
|||
/**
|
||||
* A codec to convert between strings and NBT.
|
||||
*/
|
||||
public static final Codec<NBT, String, NBTException, RuntimeException> NBT_CODEC
|
||||
= Codec.codec(encoded -> new SNBTParser(new StringReader(encoded)).parse(), NBT::toSNBT);
|
||||
public static final Codec<CompoundBinaryTag, String, IOException, IOException> NBT_CODEC
|
||||
= Codec.codec(TagStringIO.get()::asCompound, TagStringIO.get()::asString);
|
||||
|
||||
/**
|
||||
* If components should be automatically translated in outgoing packets.
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package net.minestom.server.adventure.provider;
|
||||
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||
import net.kyori.adventure.nbt.api.BinaryTagHolder;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
|
@ -9,14 +10,10 @@ import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
|||
import net.kyori.adventure.util.Codec;
|
||||
import net.minestom.server.adventure.MinestomAdventure;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBT;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
final class NBTLegacyHoverEventSerializer implements LegacyHoverEventSerializer {
|
||||
static final NBTLegacyHoverEventSerializer INSTANCE = new NBTLegacyHoverEventSerializer();
|
||||
|
@ -30,76 +27,46 @@ final class NBTLegacyHoverEventSerializer implements LegacyHoverEventSerializer
|
|||
@Override
|
||||
public HoverEvent.@NotNull ShowItem deserializeShowItem(@NotNull Component input) throws IOException {
|
||||
final String raw = PlainTextComponentSerializer.plainText().serialize(input);
|
||||
try {
|
||||
// attempt the parse
|
||||
final NBT nbt = MinestomAdventure.NBT_CODEC.decode(raw);
|
||||
if (!(nbt instanceof NBTCompound contents)) throw new IOException("contents were not a compound");
|
||||
final NBTCompound tag = contents.getCompound(ITEM_TAG);
|
||||
// attempt the parse
|
||||
final CompoundBinaryTag contents = MinestomAdventure.NBT_CODEC.decode(raw);
|
||||
final CompoundBinaryTag tag = contents.getCompound(ITEM_TAG);
|
||||
|
||||
// create the event
|
||||
return HoverEvent.ShowItem.showItem(
|
||||
Key.key(Objects.requireNonNullElse(contents.getString(ITEM_TYPE), "")),
|
||||
Objects.requireNonNullElse(contents.getByte(ITEM_COUNT), (byte) 1),
|
||||
tag == null ? null : BinaryTagHolder.encode(tag, MinestomAdventure.NBT_CODEC)
|
||||
);
|
||||
} catch (final NBTException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
// create the event
|
||||
return HoverEvent.ShowItem.showItem(
|
||||
Key.key(contents.getString(ITEM_TYPE, "")),
|
||||
contents.getByte(ITEM_COUNT, (byte) 1),
|
||||
tag.size() == 0 ? null : BinaryTagHolder.encode(tag, MinestomAdventure.NBT_CODEC)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HoverEvent.@NotNull ShowEntity deserializeShowEntity(@NotNull Component input, Codec.Decoder<Component, String, ? extends RuntimeException> componentDecoder) throws IOException {
|
||||
final String raw = PlainTextComponentSerializer.plainText().serialize(input);
|
||||
try {
|
||||
final NBT nbt = MinestomAdventure.NBT_CODEC.decode(raw);
|
||||
if (!(nbt instanceof NBTCompound contents)) throw new IOException("contents were not a compound");
|
||||
|
||||
return HoverEvent.ShowEntity.showEntity(
|
||||
Key.key(Objects.requireNonNullElse(contents.getString(ENTITY_TYPE), "")),
|
||||
UUID.fromString(Objects.requireNonNullElse(contents.getString(ENTITY_ID), "")),
|
||||
componentDecoder.decode(Objects.requireNonNullElse(contents.getString(ENTITY_NAME), ""))
|
||||
);
|
||||
} catch (NBTException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
final CompoundBinaryTag contents = MinestomAdventure.NBT_CODEC.decode(raw);
|
||||
return HoverEvent.ShowEntity.showEntity(
|
||||
Key.key(contents.getString(ENTITY_TYPE, "")),
|
||||
UUID.fromString(Objects.requireNonNullElse(contents.getString(ENTITY_ID), "")),
|
||||
componentDecoder.decode(Objects.requireNonNullElse(contents.getString(ENTITY_NAME), ""))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Component serializeShowItem(HoverEvent.@NotNull ShowItem input) throws IOException {
|
||||
AtomicReference<NBTException> exception = new AtomicReference<>(null);
|
||||
final NBTCompound tag = NBT.Compound(t -> {
|
||||
t.setString(ITEM_TYPE, input.item().asString());
|
||||
t.setByte(ITEM_COUNT, (byte) input.count());
|
||||
|
||||
final BinaryTagHolder nbt = input.nbt();
|
||||
if (nbt != null) {
|
||||
try {
|
||||
t.set(ITEM_TAG, nbt.get(MinestomAdventure.NBT_CODEC));
|
||||
} catch (NBTException e) {
|
||||
exception.set(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (exception.get() != null) {
|
||||
throw new IOException(exception.get());
|
||||
}
|
||||
|
||||
return Component.text(MinestomAdventure.NBT_CODEC.encode(tag));
|
||||
CompoundBinaryTag.Builder tag = CompoundBinaryTag.builder();
|
||||
tag.putString(ITEM_TYPE, input.item().asString());
|
||||
tag.putByte(ITEM_COUNT, (byte) input.count());
|
||||
final BinaryTagHolder nbt = input.nbt();
|
||||
if (nbt != null) tag.put(ITEM_TAG, nbt.get(MinestomAdventure.NBT_CODEC));
|
||||
return Component.text(MinestomAdventure.NBT_CODEC.encode(tag.build()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Component serializeShowEntity(HoverEvent.@NotNull ShowEntity input, Codec.Encoder<Component, String, ? extends RuntimeException> componentEncoder) {
|
||||
final NBTCompound tag = NBT.Compound(t -> {
|
||||
t.setString(ENTITY_ID, input.id().toString());
|
||||
t.setString(ENTITY_TYPE, input.type().asString());
|
||||
|
||||
final Component name = input.name();
|
||||
if (name != null) {
|
||||
t.setString(ENTITY_NAME, componentEncoder.encode(name));
|
||||
}
|
||||
});
|
||||
|
||||
return Component.text(MinestomAdventure.NBT_CODEC.encode(tag));
|
||||
public @NotNull Component serializeShowEntity(HoverEvent.@NotNull ShowEntity input, Codec.Encoder<Component, String, ? extends RuntimeException> componentEncoder) throws IOException {
|
||||
CompoundBinaryTag.Builder tag = CompoundBinaryTag.builder();
|
||||
tag.putString(ENTITY_ID, input.id().toString());
|
||||
tag.putString(ENTITY_TYPE, input.type().asString());
|
||||
final Component name = input.name();
|
||||
if (name != null) tag.putString(ENTITY_NAME, componentEncoder.encode(name));
|
||||
return Component.text(MinestomAdventure.NBT_CODEC.encode(tag.build()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package net.minestom.server.adventure.serializer.nbt;
|
||||
|
||||
import net.kyori.adventure.nbt.BinaryTag;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.ComponentSerializer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBT;
|
||||
|
||||
public interface NbtComponentSerializer extends ComponentSerializer<Component, Component, NBT> {
|
||||
public interface NbtComponentSerializer extends ComponentSerializer<Component, Component, BinaryTag> {
|
||||
static @NotNull NbtComponentSerializer nbt() {
|
||||
return NbtComponentSerializerImpl.INSTANCE;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package net.minestom.server.adventure.serializer.nbt;
|
||||
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.kyori.adventure.nbt.*;
|
||||
import net.kyori.adventure.nbt.api.BinaryTagHolder;
|
||||
import net.kyori.adventure.text.*;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
|
@ -12,14 +13,10 @@ import net.kyori.adventure.text.format.TextDecoration;
|
|||
import net.minestom.server.utils.validate.Check;
|
||||
import org.intellij.lang.annotations.Subst;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBT;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTList;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTType;
|
||||
import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
//todo write tests for me!!
|
||||
|
@ -27,32 +24,38 @@ final class NbtComponentSerializerImpl implements NbtComponentSerializer {
|
|||
static final NbtComponentSerializer INSTANCE = new NbtComponentSerializerImpl();
|
||||
|
||||
@Override
|
||||
public @NotNull Component deserialize(@NotNull NBT input) {
|
||||
public @NotNull Component deserialize(@NotNull BinaryTag input) {
|
||||
return deserializeAnyComponent(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull NBT serialize(@NotNull Component component) {
|
||||
public @NotNull BinaryTag serialize(@NotNull Component component) {
|
||||
return serializeComponent(component);
|
||||
}
|
||||
|
||||
// DESERIALIZATION
|
||||
|
||||
private @NotNull Component deserializeAnyComponent(@NotNull NBT nbt) {
|
||||
if (nbt instanceof NBTCompound compound) {
|
||||
return deserializeComponent(compound);
|
||||
} else {
|
||||
//todo raw string + list
|
||||
throw new UnsupportedOperationException("Unknown NBT type: " + nbt.getClass().getName());
|
||||
}
|
||||
private @NotNull Component deserializeAnyComponent(@NotNull BinaryTag nbt) {
|
||||
return switch (nbt) {
|
||||
case CompoundBinaryTag compound -> deserializeComponent(compound);
|
||||
case StringBinaryTag string -> Component.text(string.value());
|
||||
case ListBinaryTag list -> {
|
||||
var builder = Component.text();
|
||||
for (var element : list) {
|
||||
builder.append(deserializeAnyComponent(element));
|
||||
}
|
||||
yield builder.build();
|
||||
}
|
||||
default -> throw new UnsupportedOperationException("Unknown NBT type: " + nbt.getClass().getName());
|
||||
};
|
||||
}
|
||||
|
||||
private @NotNull Component deserializeComponent(@NotNull NBTCompound compound) {
|
||||
private @NotNull Component deserializeComponent(@NotNull CompoundBinaryTag compound) {
|
||||
ComponentBuilder<?, ?> builder;
|
||||
var type = compound.getString("type");
|
||||
if (type != null) {
|
||||
var type = compound.get("type");
|
||||
if (type instanceof StringBinaryTag sType) {
|
||||
// If type is specified, use that
|
||||
builder = switch (type) {
|
||||
builder = switch (sType.value()) {
|
||||
case "text" -> deserializeTextComponent(compound);
|
||||
case "translatable" -> deserializeTranslatableComponent(compound);
|
||||
case "score" -> deserializeScoreComponent(compound);
|
||||
|
@ -63,26 +66,30 @@ final class NbtComponentSerializerImpl implements NbtComponentSerializer {
|
|||
};
|
||||
} else {
|
||||
// Try to infer the type from the fields present.
|
||||
if (compound.containsKey("text")) {
|
||||
Set<String> keys = compound.keySet();
|
||||
if (keys.isEmpty()) {
|
||||
return Component.empty();
|
||||
} else if (keys.contains("text")) {
|
||||
builder = deserializeTextComponent(compound);
|
||||
} else if (compound.containsKey("translate")) {
|
||||
} else if (keys.contains("translate")) {
|
||||
builder = deserializeTranslatableComponent(compound);
|
||||
} else if (compound.containsKey("score")) {
|
||||
} else if (keys.contains("score")) {
|
||||
builder = deserializeScoreComponent(compound);
|
||||
} else if (compound.containsKey("selector")) {
|
||||
} else if (keys.contains("selector")) {
|
||||
builder = deserializeSelectorComponent(compound);
|
||||
} else if (compound.containsKey("keybind")) {
|
||||
} else if (keys.contains("keybind")) {
|
||||
builder = deserializeKeybindComponent(compound);
|
||||
} else if (compound.containsKey("nbt")) {
|
||||
} else if (keys.contains("nbt")) {
|
||||
builder = deserializeNbtComponent(compound);
|
||||
} else if (keys.contains("")) {
|
||||
//todo This feels like a bug, im not sure why this is created.
|
||||
builder = Component.text().content(compound.getString(""));
|
||||
} else throw new UnsupportedOperationException("Unable to infer component type");
|
||||
}
|
||||
|
||||
// Children
|
||||
var extra = compound.getList("extra");
|
||||
Check.argCondition(extra != null && !extra.getSubtagType().equals(NBTType.TAG_Compound),
|
||||
"Extra field must be a list of compounds");
|
||||
if (extra != null) {
|
||||
var extra = compound.getList("extra", BinaryTagTypes.COMPOUND);
|
||||
if (extra.size() > 0) {
|
||||
var list = new ArrayList<ComponentLike>();
|
||||
for (var child : extra) list.add(deserializeAnyComponent(child));
|
||||
builder.append(list);
|
||||
|
@ -91,7 +98,7 @@ final class NbtComponentSerializerImpl implements NbtComponentSerializer {
|
|||
// Formatting
|
||||
var style = Style.style();
|
||||
var color = compound.getString("color");
|
||||
if (color != null) {
|
||||
if (!color.isEmpty()) {
|
||||
var hexColor = TextColor.fromHexString(color);
|
||||
if (hexColor != null) {
|
||||
style.color(hexColor);
|
||||
|
@ -105,57 +112,60 @@ final class NbtComponentSerializerImpl implements NbtComponentSerializer {
|
|||
}
|
||||
}
|
||||
@Subst("minecraft:default") var font = compound.getString("font");
|
||||
if (font != null) style.font(Key.key(font));
|
||||
var bold = compound.getByte("bold");
|
||||
if (bold != null) style.decoration(TextDecoration.BOLD, bold == 1 ? TextDecoration.State.TRUE : TextDecoration.State.FALSE);
|
||||
var italic = compound.getByte("italic");
|
||||
if (italic != null) style.decoration(TextDecoration.ITALIC, italic == 1 ? TextDecoration.State.TRUE : TextDecoration.State.FALSE);
|
||||
var underlined = compound.getByte("underlined");
|
||||
if (underlined != null) style.decoration(TextDecoration.UNDERLINED, underlined == 1 ? TextDecoration.State.TRUE : TextDecoration.State.FALSE);
|
||||
var strikethrough = compound.getByte("strikethrough");
|
||||
if (strikethrough != null) style.decoration(TextDecoration.STRIKETHROUGH, strikethrough == 1 ? TextDecoration.State.TRUE : TextDecoration.State.FALSE);
|
||||
var obfuscated = compound.getByte("obfuscated");
|
||||
if (obfuscated != null) style.decoration(TextDecoration.OBFUSCATED, obfuscated == 1 ? TextDecoration.State.TRUE : TextDecoration.State.FALSE);
|
||||
if (!font.isEmpty()) style.font(Key.key(font));
|
||||
BinaryTag bold = compound.get("bold");
|
||||
if (bold instanceof ByteBinaryTag b)
|
||||
style.decoration(TextDecoration.BOLD, b.value() == 1 ? TextDecoration.State.TRUE : TextDecoration.State.FALSE);
|
||||
BinaryTag italic = compound.get("italic");
|
||||
if (italic instanceof ByteBinaryTag b)
|
||||
style.decoration(TextDecoration.ITALIC, b.value() == 1 ? TextDecoration.State.TRUE : TextDecoration.State.FALSE);
|
||||
BinaryTag underlined = compound.get("underlined");
|
||||
if (underlined instanceof ByteBinaryTag b)
|
||||
style.decoration(TextDecoration.UNDERLINED, b.value() == 1 ? TextDecoration.State.TRUE : TextDecoration.State.FALSE);
|
||||
BinaryTag strikethrough = compound.get("strikethrough");
|
||||
if (strikethrough instanceof ByteBinaryTag b)
|
||||
style.decoration(TextDecoration.STRIKETHROUGH, b.value() == 1 ? TextDecoration.State.TRUE : TextDecoration.State.FALSE);
|
||||
BinaryTag obfuscated = compound.get("obfuscated");
|
||||
if (obfuscated instanceof ByteBinaryTag b)
|
||||
style.decoration(TextDecoration.OBFUSCATED, b.value() == 1 ? TextDecoration.State.TRUE : TextDecoration.State.FALSE);
|
||||
builder.style(style.build());
|
||||
|
||||
// Interactivity
|
||||
var insertion = compound.getString("insertion");
|
||||
if (insertion != null) builder.insertion(insertion);
|
||||
if (!insertion.isEmpty()) builder.insertion(insertion);
|
||||
var clickEvent = compound.getCompound("clickEvent");
|
||||
if (clickEvent != null) builder.clickEvent(deserializeClickEvent(clickEvent));
|
||||
if (clickEvent.size() > 0) builder.clickEvent(deserializeClickEvent(clickEvent));
|
||||
var hoverEvent = compound.getCompound("hoverEvent");
|
||||
if (hoverEvent != null) builder.hoverEvent(deserializeHoverEvent(hoverEvent));
|
||||
if (hoverEvent.size() > 0) builder.hoverEvent(deserializeHoverEvent(hoverEvent));
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private @NotNull ComponentBuilder<?, ?> deserializeTextComponent(@NotNull NBTCompound compound) {
|
||||
private @NotNull ComponentBuilder<?, ?> deserializeTextComponent(@NotNull CompoundBinaryTag compound) {
|
||||
var text = compound.getString("text");
|
||||
Check.notNull(text, "Text component must have a text field");
|
||||
return Component.text().content(text);
|
||||
}
|
||||
|
||||
private @NotNull ComponentBuilder<?, ?> deserializeTranslatableComponent(@NotNull NBTCompound compound) {
|
||||
private @NotNull ComponentBuilder<?, ?> deserializeTranslatableComponent(@NotNull CompoundBinaryTag compound) {
|
||||
var key = compound.getString("translate");
|
||||
Check.notNull(key, "Translatable component must have a translate field");
|
||||
var builder = Component.translatable().key(key);
|
||||
|
||||
var fallback = compound.getString("fallback");
|
||||
if (fallback != null) builder.fallback(fallback);
|
||||
var fallback = compound.get("fallback");
|
||||
if (fallback instanceof StringBinaryTag s) builder.fallback(s.value());
|
||||
|
||||
NBTList<NBTCompound> args = compound.getList("with");
|
||||
Check.argCondition(args != null && !args.getSubtagType().equals(NBTType.TAG_Compound),
|
||||
"Translatable component with field must be a list of compounds");
|
||||
if (args != null) {
|
||||
ListBinaryTag args = compound.getList("with", BinaryTagTypes.COMPOUND);
|
||||
if (args.size() > 0) {
|
||||
var list = new ArrayList<ComponentLike>();
|
||||
for (var arg : args) list.add(deserializeComponent(arg));
|
||||
for (var arg : args) list.add(deserializeComponent((CompoundBinaryTag) arg));
|
||||
builder.arguments(list);
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private @NotNull ComponentBuilder<?, ?> deserializeScoreComponent(@NotNull NBTCompound compound) {
|
||||
private @NotNull ComponentBuilder<?, ?> deserializeScoreComponent(@NotNull CompoundBinaryTag compound) {
|
||||
var scoreCompound = compound.getCompound("score");
|
||||
Check.notNull(scoreCompound, "Score component must have a score field");
|
||||
var name = scoreCompound.getString("name");
|
||||
|
@ -165,14 +175,14 @@ final class NbtComponentSerializerImpl implements NbtComponentSerializer {
|
|||
var builder = Component.score().name(name).objective(objective);
|
||||
|
||||
var value = scoreCompound.getString("value");
|
||||
if (value != null)
|
||||
if (!value.isEmpty())
|
||||
//noinspection deprecation
|
||||
builder.value(value);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private @NotNull ComponentBuilder<?, ?> deserializeSelectorComponent(@NotNull NBTCompound compound) {
|
||||
private @NotNull ComponentBuilder<?, ?> deserializeSelectorComponent(@NotNull CompoundBinaryTag compound) {
|
||||
var selector = compound.getString("selector");
|
||||
Check.notNull(selector, "Selector component must have a selector field");
|
||||
var builder = Component.selector().pattern(selector);
|
||||
|
@ -183,17 +193,17 @@ final class NbtComponentSerializerImpl implements NbtComponentSerializer {
|
|||
return builder;
|
||||
}
|
||||
|
||||
private @NotNull ComponentBuilder<?, ?> deserializeKeybindComponent(@NotNull NBTCompound compound) {
|
||||
private @NotNull ComponentBuilder<?, ?> deserializeKeybindComponent(@NotNull CompoundBinaryTag compound) {
|
||||
var keybind = compound.getString("keybind");
|
||||
Check.notNull(keybind, "Keybind component must have a keybind field");
|
||||
return Component.keybind().keybind(keybind);
|
||||
}
|
||||
|
||||
private @NotNull ComponentBuilder<?, ?> deserializeNbtComponent(@NotNull NBTCompound compound) {
|
||||
private @NotNull ComponentBuilder<?, ?> deserializeNbtComponent(@NotNull CompoundBinaryTag compound) {
|
||||
throw new UnsupportedOperationException("NBTComponent is not implemented yet");
|
||||
}
|
||||
|
||||
private @NotNull ClickEvent deserializeClickEvent(@NotNull NBTCompound compound) {
|
||||
private @NotNull ClickEvent deserializeClickEvent(@NotNull CompoundBinaryTag compound) {
|
||||
var actionName = compound.getString("action");
|
||||
Check.notNull(actionName, "Click event must have an action field");
|
||||
var action = ClickEvent.Action.NAMES.value(actionName);
|
||||
|
@ -203,7 +213,7 @@ final class NbtComponentSerializerImpl implements NbtComponentSerializer {
|
|||
return ClickEvent.clickEvent(action, value);
|
||||
}
|
||||
|
||||
private @NotNull HoverEvent<?> deserializeHoverEvent(@NotNull NBTCompound compound) {
|
||||
private @NotNull HoverEvent<?> deserializeHoverEvent(@NotNull CompoundBinaryTag compound) {
|
||||
var actionName = compound.getString("action");
|
||||
Check.notNull(actionName, "Hover event must have an action field");
|
||||
var contents = compound.getCompound("contents");
|
||||
|
@ -216,13 +226,12 @@ final class NbtComponentSerializerImpl implements NbtComponentSerializer {
|
|||
@Subst("minecraft:stick") var id = contents.getString("id");
|
||||
Check.notNull(id, "Show item hover event must have an id field");
|
||||
var count = contents.getInt("count");
|
||||
var countInt = count == null ? 1 : count;
|
||||
var tag = contents.getString("tag");
|
||||
var binaryTag = tag == null ? null : BinaryTagHolder.binaryTagHolder(tag);
|
||||
return HoverEvent.showItem(Key.key(id), countInt, binaryTag);
|
||||
var binaryTag = tag.isEmpty() ? null : BinaryTagHolder.binaryTagHolder(tag);
|
||||
return HoverEvent.showItem(Key.key(id), count, binaryTag);
|
||||
} else if (action == HoverEvent.Action.SHOW_ENTITY) {
|
||||
var name = contents.getCompound("name");
|
||||
var nameComponent = name == null ? null : deserializeComponent(name);
|
||||
var nameComponent = name.size() == 0 ? null : deserializeComponent(name);
|
||||
@Subst("minecraft:pig") var type = contents.getString("type");
|
||||
Check.notNull(type, "Show entity hover event must have a type field");
|
||||
var id = contents.getString("id");
|
||||
|
@ -235,36 +244,36 @@ final class NbtComponentSerializerImpl implements NbtComponentSerializer {
|
|||
|
||||
// SERIALIZATION
|
||||
|
||||
private @NotNull NBT serializeComponent(@NotNull Component component) {
|
||||
MutableNBTCompound compound = new MutableNBTCompound();
|
||||
private @NotNull CompoundBinaryTag serializeComponent(@NotNull Component component) {
|
||||
CompoundBinaryTag.Builder compound = CompoundBinaryTag.builder();
|
||||
|
||||
// Base component types
|
||||
if (component instanceof TextComponent text) {
|
||||
compound.setString("type", "text");
|
||||
compound.setString("text", text.content());
|
||||
compound.putString("type", "text");
|
||||
compound.putString("text", text.content());
|
||||
} else if (component instanceof TranslatableComponent translatable) {
|
||||
compound.setString("type", "translatable");
|
||||
compound.setString("translate", translatable.key());
|
||||
compound.putString("type", "translatable");
|
||||
compound.putString("translate", translatable.key());
|
||||
var fallback = translatable.fallback();
|
||||
if (fallback != null) compound.setString("fallback", fallback);
|
||||
if (fallback != null) compound.putString("fallback", fallback);
|
||||
var args = translatable.arguments();
|
||||
if (!args.isEmpty()) compound.set("with", serializeTranslationArgs(args));
|
||||
if (!args.isEmpty()) compound.put("with", serializeTranslationArgs(args));
|
||||
} else if (component instanceof ScoreComponent score) {
|
||||
compound.setString("type", "score");
|
||||
var scoreCompound = new MutableNBTCompound();
|
||||
scoreCompound.setString("name", score.name());
|
||||
scoreCompound.setString("objective", score.objective());
|
||||
compound.putString("type", "score");
|
||||
CompoundBinaryTag.Builder scoreCompound = CompoundBinaryTag.builder();
|
||||
scoreCompound.putString("name", score.name());
|
||||
scoreCompound.putString("objective", score.objective());
|
||||
@SuppressWarnings("deprecation") var value = score.value();
|
||||
if (value != null) scoreCompound.setString("value", value);
|
||||
compound.set("score", scoreCompound.toCompound());
|
||||
if (value != null) scoreCompound.putString("value", value);
|
||||
compound.put("score", scoreCompound.build());
|
||||
} else if (component instanceof SelectorComponent selector) {
|
||||
compound.setString("type", "selector");
|
||||
compound.setString("selector", selector.pattern());
|
||||
compound.putString("type", "selector");
|
||||
compound.putString("selector", selector.pattern());
|
||||
var separator = selector.separator();
|
||||
if (separator != null) compound.set("separator", serializeComponent(separator));
|
||||
if (separator != null) compound.put("separator", serializeComponent(separator));
|
||||
} else if (component instanceof KeybindComponent keybind) {
|
||||
compound.setString("type", "keybind");
|
||||
compound.setString("keybind", keybind.keybind());
|
||||
compound.putString("type", "keybind");
|
||||
compound.putString("keybind", keybind.keybind());
|
||||
} else if (component instanceof NBTComponent<?, ?> nbt) {
|
||||
//todo
|
||||
throw new UnsupportedOperationException("NBTComponent is not implemented yet");
|
||||
|
@ -274,10 +283,10 @@ final class NbtComponentSerializerImpl implements NbtComponentSerializer {
|
|||
|
||||
// Children
|
||||
if (!component.children().isEmpty()) {
|
||||
var children = new ArrayList<NBT>();
|
||||
ListBinaryTag.Builder<CompoundBinaryTag> children = ListBinaryTag.builder(BinaryTagTypes.COMPOUND);
|
||||
for (var child : component.children())
|
||||
children.add(serializeComponent(child));
|
||||
compound.set("extra", new NBTList<>(NBTType.TAG_Compound, children));
|
||||
compound.put("extra", children.build());
|
||||
}
|
||||
|
||||
// Formatting
|
||||
|
@ -285,94 +294,89 @@ final class NbtComponentSerializerImpl implements NbtComponentSerializer {
|
|||
var color = style.color();
|
||||
if (color != null) {
|
||||
if (color instanceof NamedTextColor named) {
|
||||
compound.setString("color", named.toString());
|
||||
compound.putString("color", named.toString());
|
||||
} else {
|
||||
compound.setString("color", color.asHexString());
|
||||
compound.putString("color", color.asHexString());
|
||||
}
|
||||
}
|
||||
var font = style.font();
|
||||
if (font != null)
|
||||
compound.setString("font", font.toString());
|
||||
compound.putString("font", font.toString());
|
||||
var bold = style.decoration(TextDecoration.BOLD);
|
||||
if (bold != TextDecoration.State.NOT_SET)
|
||||
setBool(compound, "bold", bold == TextDecoration.State.TRUE);
|
||||
compound.putBoolean("bold", bold == TextDecoration.State.TRUE);
|
||||
var italic = style.decoration(TextDecoration.ITALIC);
|
||||
if (italic != TextDecoration.State.NOT_SET)
|
||||
setBool(compound, "italic", italic == TextDecoration.State.TRUE);
|
||||
compound.putBoolean("italic", italic == TextDecoration.State.TRUE);
|
||||
var underlined = style.decoration(TextDecoration.UNDERLINED);
|
||||
if (underlined != TextDecoration.State.NOT_SET)
|
||||
setBool(compound, "underlined", underlined == TextDecoration.State.TRUE);
|
||||
compound.putBoolean("underlined", underlined == TextDecoration.State.TRUE);
|
||||
var strikethrough = style.decoration(TextDecoration.STRIKETHROUGH);
|
||||
if (strikethrough != TextDecoration.State.NOT_SET)
|
||||
setBool(compound, "strikethrough", strikethrough == TextDecoration.State.TRUE);
|
||||
compound.putBoolean("strikethrough", strikethrough == TextDecoration.State.TRUE);
|
||||
var obfuscated = style.decoration(TextDecoration.OBFUSCATED);
|
||||
if (obfuscated != TextDecoration.State.NOT_SET)
|
||||
setBool(compound, "obfuscated", obfuscated == TextDecoration.State.TRUE);
|
||||
compound.putBoolean("obfuscated", obfuscated == TextDecoration.State.TRUE);
|
||||
|
||||
// Interactivity
|
||||
var insertion = component.insertion();
|
||||
if (insertion != null) compound.setString("insertion", insertion);
|
||||
if (insertion != null) compound.putString("insertion", insertion);
|
||||
var clickEvent = component.clickEvent();
|
||||
if (clickEvent != null) compound.set("clickEvent", serializeClickEvent(clickEvent));
|
||||
if (clickEvent != null) compound.put("clickEvent", serializeClickEvent(clickEvent));
|
||||
var hoverEvent = component.hoverEvent();
|
||||
if (hoverEvent != null) compound.set("hoverEvent", serializeHoverEvent(hoverEvent));
|
||||
if (hoverEvent != null) compound.put("hoverEvent", serializeHoverEvent(hoverEvent));
|
||||
|
||||
return compound.toCompound();
|
||||
return compound.build();
|
||||
}
|
||||
|
||||
private @NotNull NBT serializeTranslationArgs(@NotNull Collection<TranslationArgument> args) {
|
||||
var list = new ArrayList<NBT>();
|
||||
private @NotNull BinaryTag serializeTranslationArgs(@NotNull Collection<TranslationArgument> args) {
|
||||
ListBinaryTag.Builder<CompoundBinaryTag> argList = ListBinaryTag.builder(BinaryTagTypes.COMPOUND);
|
||||
for (var arg : args)
|
||||
list.add(serializeComponent(arg.asComponent()));
|
||||
return new NBTList<>(NBTType.TAG_Compound, list);
|
||||
argList.add(serializeComponent(arg.asComponent()));
|
||||
return argList.build();
|
||||
}
|
||||
|
||||
private @NotNull NBT serializeClickEvent(@NotNull ClickEvent event) {
|
||||
var compound = new MutableNBTCompound();
|
||||
compound.setString("action", event.action().toString());
|
||||
compound.setString("value", event.value());
|
||||
return compound.toCompound();
|
||||
private @NotNull BinaryTag serializeClickEvent(@NotNull ClickEvent event) {
|
||||
return CompoundBinaryTag.builder()
|
||||
.putString("action", event.action().toString())
|
||||
.putString("value", event.value())
|
||||
.build();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private @NotNull NBT serializeHoverEvent(@NotNull HoverEvent<?> event) {
|
||||
var compound = new MutableNBTCompound();
|
||||
private @NotNull BinaryTag serializeHoverEvent(@NotNull HoverEvent<?> event) {
|
||||
CompoundBinaryTag.Builder compound = CompoundBinaryTag.builder();
|
||||
|
||||
//todo surely there is a better way to do this?
|
||||
compound.setString("action", event.action().toString());
|
||||
compound.putString("action", event.action().toString());
|
||||
if (event.action() == HoverEvent.Action.SHOW_TEXT) {
|
||||
var value = ((HoverEvent<Component>) event).value();
|
||||
compound.set("contents", serializeComponent(value));
|
||||
compound.put("contents", serializeComponent(value));
|
||||
} else if (event.action() == HoverEvent.Action.SHOW_ITEM) {
|
||||
var value = ((HoverEvent<HoverEvent.ShowItem>) event).value();
|
||||
|
||||
var itemCompound = new MutableNBTCompound();
|
||||
itemCompound.setString("id", value.item().asString());
|
||||
if (value.count() != 1) itemCompound.setInt("count", value.count());
|
||||
CompoundBinaryTag.Builder itemCompound = CompoundBinaryTag.builder();
|
||||
itemCompound.putString("id", value.item().asString());
|
||||
if (value.count() != 1) itemCompound.putInt("count", value.count());
|
||||
var tag = value.nbt();
|
||||
if (tag != null) itemCompound.setString("tag", tag.string());
|
||||
if (tag != null) itemCompound.putString("tag", tag.string());
|
||||
|
||||
compound.set("contents", itemCompound.toCompound());
|
||||
compound.put("contents", itemCompound.build());
|
||||
} else if (event.action() == HoverEvent.Action.SHOW_ENTITY) {
|
||||
var value = ((HoverEvent<HoverEvent.ShowEntity>) event).value();
|
||||
|
||||
var entityCompound = new MutableNBTCompound();
|
||||
CompoundBinaryTag.Builder entityCompound = CompoundBinaryTag.builder();
|
||||
var name = value.name();
|
||||
if (name != null) entityCompound.set("name", serializeComponent(name));
|
||||
entityCompound.setString("type", value.type().asString());
|
||||
entityCompound.setString("id", value.id().toString());
|
||||
if (name != null) entityCompound.put("name", serializeComponent(name));
|
||||
entityCompound.putString("type", value.type().asString());
|
||||
entityCompound.putString("id", value.id().toString());
|
||||
|
||||
compound.set("contents", entityCompound.toCompound());
|
||||
compound.put("contents", entityCompound.build());
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Unknown hover event action: " + event.action());
|
||||
}
|
||||
|
||||
return compound.toCompound();
|
||||
return compound.build();
|
||||
}
|
||||
|
||||
private void setBool(@NotNull MutableNBTCompound compound, @NotNull String key, boolean value) {
|
||||
compound.setByte(key, value ? (byte) 1 : 0);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
package net.minestom.server.attribute;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Represents a {@link net.minestom.server.entity.LivingEntity living entity} attribute.
|
||||
*/
|
||||
public record Attribute(String key, float defaultValue, float maxValue) {
|
||||
private static final Map<String, Attribute> ATTRIBUTES = new ConcurrentHashMap<>();
|
||||
|
||||
public static final Attribute MAX_HEALTH = (new Attribute("generic.max_health", 20, 1024)).register();
|
||||
public static final Attribute FOLLOW_RANGE = (new Attribute("generic.follow_range", 32, 2048)).register();
|
||||
public static final Attribute KNOCKBACK_RESISTANCE = (new Attribute("generic.knockback_resistance", 0, 1)).register();
|
||||
public static final Attribute MOVEMENT_SPEED = (new Attribute("generic.movement_speed", 0.25f, 1024)).register();
|
||||
public static final Attribute ATTACK_DAMAGE = (new Attribute("generic.attack_damage", 2, 2048)).register();
|
||||
public static final Attribute ATTACK_SPEED = (new Attribute("generic.attack_speed", 4, 1024)).register();
|
||||
public static final Attribute FLYING_SPEED = (new Attribute("generic.flying_speed", 0.4f, 1024)).register();
|
||||
public static final Attribute ARMOR = (new Attribute("generic.armor", 0, 30)).register();
|
||||
public static final Attribute ARMOR_TOUGHNESS = (new Attribute("generic.armor_toughness", 0, 20)).register();
|
||||
public static final Attribute ATTACK_KNOCKBACK = (new Attribute("generic.attack_knockback", 0, 5)).register();
|
||||
public static final Attribute LUCK = (new Attribute("generic.luck", 0, 1024)).register();
|
||||
public static final Attribute HORSE_JUMP_STRENGTH = (new Attribute("horse.jump_strength", 0.7f, 2)).register();
|
||||
public static final Attribute ZOMBIE_SPAWN_REINFORCEMENTS = (new Attribute("zombie.spawn_reinforcements", 0, 1)).register();
|
||||
|
||||
public Attribute {
|
||||
if (defaultValue > maxValue) {
|
||||
throw new IllegalArgumentException("Default value cannot be greater than the maximum allowed");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register this attribute.
|
||||
*
|
||||
* @return this attribute
|
||||
* @see #fromKey(String)
|
||||
* @see #values()
|
||||
*/
|
||||
public @NotNull Attribute register() {
|
||||
ATTRIBUTES.put(key, this);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an attribute by its key.
|
||||
*
|
||||
* @param key the key of the attribute
|
||||
* @return the attribute for the key or null if not any
|
||||
*/
|
||||
public static @Nullable Attribute fromKey(@NotNull String key) {
|
||||
return ATTRIBUTES.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all registered attributes.
|
||||
*
|
||||
* @return an array containing all registered attributes
|
||||
*/
|
||||
public static @NotNull Collection<@NotNull Attribute> values() {
|
||||
return ATTRIBUTES.values();
|
||||
}
|
||||
}
|
|
@ -298,7 +298,7 @@ final class BlockCollision {
|
|||
// don't fall out of if statement, we could end up redundantly grabbing a block, and we only need to
|
||||
// collision check against the current shape since the below shape isn't tall
|
||||
if (belowShape.relativeEnd().y() > 1) {
|
||||
// we should always check both shapes, so no short-circuit here, to handle cases where the bounding box
|
||||
// we should always check both shapes, so no short-circuit here, to handle properties where the bounding box
|
||||
// hits the current solid but misses the tall solid
|
||||
return belowShape.intersectBoxSwept(entityPosition, entityVelocity, belowPos, boundingBox, finalResult) |
|
||||
(currentCollidable && currentShape.intersectBoxSwept(entityPosition, entityVelocity, currentPos, boundingBox, finalResult));
|
||||
|
|
|
@ -1,16 +1,20 @@
|
|||
package net.minestom.server.command.builder.arguments.minecraft;
|
||||
|
||||
import net.kyori.adventure.nbt.BinaryTag;
|
||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||
import net.kyori.adventure.nbt.TagStringIOExt;
|
||||
import net.minestom.server.command.CommandSender;
|
||||
import net.minestom.server.command.builder.arguments.Argument;
|
||||
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||
import net.minestom.server.item.ItemComponent;
|
||||
import net.minestom.server.item.ItemComponentMap;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.item.Material;
|
||||
import net.minestom.server.item.component.CustomData;
|
||||
import net.minestom.server.utils.NamespaceID;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTException;
|
||||
import org.jglrxavpok.hephaistos.parser.SNBTParser;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Argument which can be used to retrieve an {@link ItemStack} from its material and with NBT data.
|
||||
|
@ -24,6 +28,7 @@ public class ArgumentItemStack extends Argument<ItemStack> {
|
|||
public static final int NO_MATERIAL = 1;
|
||||
public static final int INVALID_NBT = 2;
|
||||
public static final int INVALID_MATERIAL = 3;
|
||||
public static final int INVALID_COMPONENT = 4;
|
||||
|
||||
public ArgumentItemStack(String id) {
|
||||
super(id, true);
|
||||
|
@ -45,39 +50,108 @@ public class ArgumentItemStack extends Argument<ItemStack> {
|
|||
*/
|
||||
@Deprecated
|
||||
public static ItemStack staticParse(@NotNull String input) throws ArgumentSyntaxException {
|
||||
final int nbtIndex = input.indexOf("{");
|
||||
var reader = new StringReader(input);
|
||||
|
||||
if (nbtIndex == 0)
|
||||
throw new ArgumentSyntaxException("The item needs a material", input, NO_MATERIAL);
|
||||
|
||||
if (nbtIndex == -1) {
|
||||
// Only material name
|
||||
final Material material = Material.fromNamespaceId(input);
|
||||
if (material == null)
|
||||
throw new ArgumentSyntaxException("Material is invalid", input, INVALID_MATERIAL);
|
||||
return ItemStack.of(material);
|
||||
} else {
|
||||
// Material plus additional NBT
|
||||
final String materialName = input.substring(0, nbtIndex);
|
||||
final Material material = Material.fromNamespaceId(materialName);
|
||||
if (material == null)
|
||||
throw new ArgumentSyntaxException("Material is invalid", input, INVALID_MATERIAL);
|
||||
|
||||
final String sNBT = input.substring(nbtIndex).replace("\\\"", "\"");
|
||||
|
||||
NBTCompound compound;
|
||||
try {
|
||||
compound = (NBTCompound) new SNBTParser(new StringReader(sNBT)).parse();
|
||||
} catch (NBTException e) {
|
||||
throw new ArgumentSyntaxException("Item NBT is invalid", input, INVALID_NBT);
|
||||
}
|
||||
|
||||
return ItemStack.fromNBT(material, compound);
|
||||
final Material material = Material.fromNamespaceId(reader.readNamespaceId());
|
||||
if (material == null)
|
||||
throw new ArgumentSyntaxException("Material is invalid", input, INVALID_MATERIAL);
|
||||
if (!reader.hasMore()) {
|
||||
return ItemStack.of(material); // Nothing else, we have our item
|
||||
}
|
||||
|
||||
ItemComponentMap.Builder components = ItemComponentMap.builder();
|
||||
|
||||
// Parse the declared components
|
||||
if (reader.peek() == '[') {
|
||||
reader.consume('[');
|
||||
do {
|
||||
final NamespaceID componentId = reader.readNamespaceId();
|
||||
final ItemComponent<?> component = ItemComponent.fromNamespaceId(componentId);
|
||||
if (component == null)
|
||||
throw new ArgumentSyntaxException("Unknown item component", input, INVALID_COMPONENT);
|
||||
|
||||
reader.consume('=');
|
||||
|
||||
final BinaryTag nbt = reader.readTag();
|
||||
components.set(component, component.read(nbt));
|
||||
|
||||
if (reader.peek() != ']')
|
||||
reader.consume(',');
|
||||
} while (reader.peek() != ']');
|
||||
reader.consume(']');
|
||||
}
|
||||
|
||||
// Parse the NBT
|
||||
if (reader.hasMore() && reader.peek() == '{') {
|
||||
final BinaryTag nbt = reader.readTag();
|
||||
if (!(nbt instanceof CompoundBinaryTag compound))
|
||||
throw new ArgumentSyntaxException("Item NBT must be compound", input, INVALID_NBT);
|
||||
|
||||
final CompoundBinaryTag customData = CompoundBinaryTag.builder()
|
||||
.put(components.get(ItemComponent.CUSTOM_DATA, CustomData.EMPTY).nbt())
|
||||
.put(compound)
|
||||
.build();
|
||||
components.set(ItemComponent.CUSTOM_DATA, new CustomData(customData));
|
||||
}
|
||||
|
||||
if (reader.hasMore())
|
||||
throw new ArgumentSyntaxException("Unexpected remaining input", input, INVALID_NBT);
|
||||
|
||||
return ItemStack.of(material, components.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("ItemStack<%s>", getId());
|
||||
}
|
||||
|
||||
private static class StringReader {
|
||||
private String input;
|
||||
private int index = 0;
|
||||
|
||||
public StringReader(@NotNull String input) {
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
public boolean hasMore() {
|
||||
return index < input.length();
|
||||
}
|
||||
|
||||
public char peek() {
|
||||
if (!hasMore()) {
|
||||
throw new ArgumentSyntaxException("Unexpected end of input", input, INVALID_NBT);
|
||||
}
|
||||
|
||||
return input.charAt(index);
|
||||
}
|
||||
|
||||
public void consume(char c) {
|
||||
char next = peek();
|
||||
if (next != c) {
|
||||
throw new ArgumentSyntaxException("Expected '" + c + "', got '" + next + "'", input, INVALID_NBT);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
public @NotNull NamespaceID readNamespaceId() {
|
||||
char c;
|
||||
int start = index;
|
||||
while (hasMore() && (c = peek()) != '{' && c != '[' && c != '=') {
|
||||
index++;
|
||||
}
|
||||
return NamespaceID.from(input.substring(start, index));
|
||||
}
|
||||
|
||||
public @NotNull BinaryTag readTag() {
|
||||
try {
|
||||
var result = TagStringIOExt.readTagEmbedded(input.substring(index));
|
||||
this.input = result.getValue();
|
||||
this.index = 0;
|
||||
|
||||
return result.getKey();
|
||||
} catch (IOException e) {
|
||||
throw new ArgumentSyntaxException("Invalid NBT", input, INVALID_NBT);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,21 @@
|
|||
package net.minestom.server.command.builder.arguments.minecraft;
|
||||
|
||||
import net.kyori.adventure.nbt.BinaryTag;
|
||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||
import net.kyori.adventure.nbt.TagStringIO;
|
||||
import net.minestom.server.command.CommandSender;
|
||||
import net.minestom.server.command.builder.arguments.Argument;
|
||||
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBT;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTException;
|
||||
import org.jglrxavpok.hephaistos.parser.SNBTParser;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Argument used to retrieve a {@link NBTCompound} if you need key-value data.
|
||||
* Argument used to retrieve a {@link CompoundBinaryTag} if you need key-value data.
|
||||
* <p>
|
||||
* Example: {display:{Name:"{\"text\":\"Sword of Power\"}"}}
|
||||
*/
|
||||
public class ArgumentNbtCompoundTag extends Argument<NBTCompound> {
|
||||
public class ArgumentNbtCompoundTag extends Argument<CompoundBinaryTag> {
|
||||
|
||||
public static final int INVALID_NBT = 1;
|
||||
|
||||
|
@ -26,15 +25,15 @@ public class ArgumentNbtCompoundTag extends Argument<NBTCompound> {
|
|||
|
||||
@NotNull
|
||||
@Override
|
||||
public NBTCompound parse(@NotNull CommandSender sender, @NotNull String input) throws ArgumentSyntaxException {
|
||||
public CompoundBinaryTag parse(@NotNull CommandSender sender, @NotNull String input) throws ArgumentSyntaxException {
|
||||
try {
|
||||
NBT nbt = new SNBTParser(new StringReader(input)).parse();
|
||||
BinaryTag nbt = TagStringIO.get().asCompound(input);
|
||||
|
||||
if (!(nbt instanceof NBTCompound))
|
||||
if (!(nbt instanceof CompoundBinaryTag compound))
|
||||
throw new ArgumentSyntaxException("NBTCompound is invalid", input, INVALID_NBT);
|
||||
|
||||
return (NBTCompound) nbt;
|
||||
} catch (NBTException e) {
|
||||
return compound;
|
||||
} catch (IOException e) {
|
||||
throw new ArgumentSyntaxException("NBTCompound is invalid", input, INVALID_NBT);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,23 +1,22 @@
|
|||
package net.minestom.server.command.builder.arguments.minecraft;
|
||||
|
||||
import net.kyori.adventure.nbt.BinaryTag;
|
||||
import net.kyori.adventure.nbt.TagStringIOExt;
|
||||
import net.minestom.server.command.CommandSender;
|
||||
import net.minestom.server.command.builder.arguments.Argument;
|
||||
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBT;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTException;
|
||||
import org.jglrxavpok.hephaistos.parser.SNBTParser;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Argument used to retrieve a {@link NBT} based object, can be any kind of tag like
|
||||
* {@link org.jglrxavpok.hephaistos.nbt.NBTCompound}, {@link org.jglrxavpok.hephaistos.nbt.NBTList},
|
||||
* {@link org.jglrxavpok.hephaistos.nbt.NBTInt}, etc...
|
||||
* Argument used to retrieve a {@link BinaryTag} based object, can be any kind of tag like
|
||||
* {@link net.kyori.adventure.nbt.CompoundBinaryTag}, {@link net.kyori.adventure.nbt.ListBinaryTag},
|
||||
* {@link net.kyori.adventure.nbt.IntBinaryTag}, etc...
|
||||
* <p>
|
||||
* Example: {display:{Name:"{\"text\":\"Sword of Power\"}"}} or [{display:{Name:"{\"text\":\"Sword of Power\"}"}}]
|
||||
*/
|
||||
public class ArgumentNbtTag extends Argument<NBT> {
|
||||
public class ArgumentNbtTag extends Argument<BinaryTag> {
|
||||
|
||||
public static final int INVALID_NBT = 1;
|
||||
|
||||
|
@ -27,10 +26,10 @@ public class ArgumentNbtTag extends Argument<NBT> {
|
|||
|
||||
@NotNull
|
||||
@Override
|
||||
public NBT parse(@NotNull CommandSender sender, @NotNull String input) throws ArgumentSyntaxException {
|
||||
public BinaryTag parse(@NotNull CommandSender sender, @NotNull String input) throws ArgumentSyntaxException {
|
||||
try {
|
||||
return new SNBTParser(new StringReader(input)).parse();
|
||||
} catch (NBTException e) {
|
||||
return TagStringIOExt.readTag(input);
|
||||
} catch (IOException e) {
|
||||
throw new ArgumentSyntaxException("Invalid NBT", input, INVALID_NBT);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package net.minestom.server.command.builder.arguments.minecraft.registry;
|
||||
|
||||
import net.minestom.server.item.Enchantment;
|
||||
import net.minestom.server.item.enchant.Enchantment;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
|
|
|
@ -1385,13 +1385,11 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
|
|||
|
||||
/**
|
||||
* Gets the entity eye height.
|
||||
* <p>
|
||||
* Default to {@link BoundingBox#height()}x0.85
|
||||
*
|
||||
* @return the entity eye height
|
||||
*/
|
||||
public double getEyeHeight() {
|
||||
return getPose() == Pose.SLEEPING ? 0.2 : (boundingBox.height() * 0.85);
|
||||
return getPose() == Pose.SLEEPING ? 0.2 : entityType.registry().eyeHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,7 +7,6 @@ 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;
|
||||
import net.minestom.server.entity.metadata.projectile.*;
|
||||
import net.minestom.server.entity.metadata.display.BlockDisplayMeta;
|
||||
import net.minestom.server.entity.metadata.display.ItemDisplayMeta;
|
||||
import net.minestom.server.entity.metadata.display.TextDisplayMeta;
|
||||
|
@ -20,11 +19,13 @@ 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.BoggedMeta;
|
||||
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.*;
|
||||
import net.minestom.server.entity.metadata.other.*;
|
||||
import net.minestom.server.entity.metadata.projectile.*;
|
||||
import net.minestom.server.entity.metadata.villager.VillagerMeta;
|
||||
import net.minestom.server.entity.metadata.villager.WanderingTraderMeta;
|
||||
import net.minestom.server.entity.metadata.water.AxolotlMeta;
|
||||
|
@ -39,8 +40,6 @@ import java.util.Collection;
|
|||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import static java.util.Map.entry;
|
||||
|
||||
record EntityTypeImpl(Registry.EntityEntry registry) implements EntityType {
|
||||
private static final Registry.Container<EntityType> CONTAINER = Registry.createStaticContainer(Registry.Resource.ENTITIES,
|
||||
(namespace, properties) -> new EntityTypeImpl(Registry.entity(namespace, properties)));
|
||||
|
@ -73,132 +72,136 @@ record EntityTypeImpl(Registry.EntityEntry registry) implements EntityType {
|
|||
|
||||
private static Map<String, BiFunction<Entity, Metadata, EntityMeta>> createMetaMap() {
|
||||
return Map.<String, BiFunction<Entity, Metadata, EntityMeta>>ofEntries(
|
||||
entry("minecraft:allay", AllayMeta::new),
|
||||
entry("minecraft:area_effect_cloud", AreaEffectCloudMeta::new),
|
||||
entry("minecraft:armor_stand", ArmorStandMeta::new),
|
||||
entry("minecraft:arrow", ArrowMeta::new),
|
||||
entry("minecraft:axolotl", AxolotlMeta::new),
|
||||
entry("minecraft:bat", BatMeta::new),
|
||||
entry("minecraft:bee", BeeMeta::new),
|
||||
entry("minecraft:blaze", BlazeMeta::new),
|
||||
entry("minecraft:block_display", BlockDisplayMeta::new),
|
||||
entry("minecraft:boat", BoatMeta::new),
|
||||
entry("minecraft:breeze", BreezeMeta::new),
|
||||
entry("minecraft:chest_boat", BoatMeta::new),
|
||||
entry("minecraft:camel", CamelMeta::new),
|
||||
entry("minecraft:cat", CatMeta::new),
|
||||
entry("minecraft:cave_spider", CaveSpiderMeta::new),
|
||||
entry("minecraft:chicken", ChickenMeta::new),
|
||||
entry("minecraft:cod", CodMeta::new),
|
||||
entry("minecraft:cow", CowMeta::new),
|
||||
entry("minecraft:creeper", CreeperMeta::new),
|
||||
entry("minecraft:dolphin", DolphinMeta::new),
|
||||
entry("minecraft:donkey", DonkeyMeta::new),
|
||||
entry("minecraft:dragon_fireball", DragonFireballMeta::new),
|
||||
entry("minecraft:drowned", DrownedMeta::new),
|
||||
entry("minecraft:elder_guardian", ElderGuardianMeta::new),
|
||||
entry("minecraft:end_crystal", EndCrystalMeta::new),
|
||||
entry("minecraft:ender_dragon", EnderDragonMeta::new),
|
||||
entry("minecraft:enderman", EndermanMeta::new),
|
||||
entry("minecraft:endermite", EndermiteMeta::new),
|
||||
entry("minecraft:evoker", EvokerMeta::new),
|
||||
entry("minecraft:evoker_fangs", EvokerFangsMeta::new),
|
||||
entry("minecraft:experience_orb", ExperienceOrbMeta::new),
|
||||
entry("minecraft:eye_of_ender", EyeOfEnderMeta::new),
|
||||
entry("minecraft:falling_block", FallingBlockMeta::new),
|
||||
entry("minecraft:firework_rocket", FireworkRocketMeta::new),
|
||||
entry("minecraft:fox", FoxMeta::new),
|
||||
entry("minecraft:frog", FrogMeta::new),
|
||||
entry("minecraft:ghast", GhastMeta::new),
|
||||
entry("minecraft:giant", GiantMeta::new),
|
||||
entry("minecraft:glow_item_frame", GlowItemFrameMeta::new),
|
||||
entry("minecraft:glow_squid", GlowSquidMeta::new),
|
||||
entry("minecraft:goat", GoatMeta::new),
|
||||
entry("minecraft:guardian", GuardianMeta::new),
|
||||
entry("minecraft:hoglin", HoglinMeta::new),
|
||||
entry("minecraft:horse", HorseMeta::new),
|
||||
entry("minecraft:husk", HuskMeta::new),
|
||||
entry("minecraft:illusioner", IllusionerMeta::new),
|
||||
entry("minecraft:interaction", InteractionMeta::new),
|
||||
entry("minecraft:iron_golem", IronGolemMeta::new),
|
||||
entry("minecraft:item", ItemEntityMeta::new),
|
||||
entry("minecraft:item_display", ItemDisplayMeta::new),
|
||||
entry("minecraft:item_frame", ItemFrameMeta::new),
|
||||
entry("minecraft:fireball", FireballMeta::new),
|
||||
entry("minecraft:leash_knot", LeashKnotMeta::new),
|
||||
entry("minecraft:lightning_bolt", LightningBoltMeta::new),
|
||||
entry("minecraft:llama", LlamaMeta::new),
|
||||
entry("minecraft:llama_spit", LlamaSpitMeta::new),
|
||||
entry("minecraft:magma_cube", MagmaCubeMeta::new),
|
||||
entry("minecraft:marker", MarkerMeta::new),
|
||||
entry("minecraft:minecart", MinecartMeta::new),
|
||||
entry("minecraft:chest_minecart", ChestMinecartMeta::new),
|
||||
entry("minecraft:command_block_minecart", CommandBlockMinecartMeta::new),
|
||||
entry("minecraft:furnace_minecart", FurnaceMinecartMeta::new),
|
||||
entry("minecraft:hopper_minecart", HopperMinecartMeta::new),
|
||||
entry("minecraft:spawner_minecart", SpawnerMinecartMeta::new),
|
||||
entry("minecraft:text_display", TextDisplayMeta::new),
|
||||
entry("minecraft:tnt_minecart", TntMinecartMeta::new),
|
||||
entry("minecraft:mule", MuleMeta::new),
|
||||
entry("minecraft:mooshroom", MooshroomMeta::new),
|
||||
entry("minecraft:ocelot", OcelotMeta::new),
|
||||
entry("minecraft:painting", PaintingMeta::new),
|
||||
entry("minecraft:panda", PandaMeta::new),
|
||||
entry("minecraft:parrot", ParrotMeta::new),
|
||||
entry("minecraft:phantom", PhantomMeta::new),
|
||||
entry("minecraft:pig", PigMeta::new),
|
||||
entry("minecraft:piglin", PiglinMeta::new),
|
||||
entry("minecraft:piglin_brute", PiglinBruteMeta::new),
|
||||
entry("minecraft:pillager", PillagerMeta::new),
|
||||
entry("minecraft:polar_bear", PolarBearMeta::new),
|
||||
entry("minecraft:tnt", PrimedTntMeta::new),
|
||||
entry("minecraft:pufferfish", PufferfishMeta::new),
|
||||
entry("minecraft:rabbit", RabbitMeta::new),
|
||||
entry("minecraft:ravager", RavagerMeta::new),
|
||||
entry("minecraft:salmon", SalmonMeta::new),
|
||||
entry("minecraft:sheep", SheepMeta::new),
|
||||
entry("minecraft:shulker", ShulkerMeta::new),
|
||||
entry("minecraft:shulker_bullet", ShulkerBulletMeta::new),
|
||||
entry("minecraft:silverfish", SilverfishMeta::new),
|
||||
entry("minecraft:skeleton", SkeletonMeta::new),
|
||||
entry("minecraft:skeleton_horse", SkeletonHorseMeta::new),
|
||||
entry("minecraft:slime", SlimeMeta::new),
|
||||
entry("minecraft:small_fireball", SmallFireballMeta::new),
|
||||
entry("minecraft:sniffer", SnifferMeta::new),
|
||||
entry("minecraft:snow_golem", SnowGolemMeta::new),
|
||||
entry("minecraft:snowball", SnowballMeta::new),
|
||||
entry("minecraft:spectral_arrow", SpectralArrowMeta::new),
|
||||
entry("minecraft:spider", SpiderMeta::new),
|
||||
entry("minecraft:squid", SquidMeta::new),
|
||||
entry("minecraft:stray", StrayMeta::new),
|
||||
entry("minecraft:strider", StriderMeta::new),
|
||||
entry("minecraft:tadpole", TadpoleMeta::new),
|
||||
entry("minecraft:egg", ThrownEggMeta::new),
|
||||
entry("minecraft:ender_pearl", ThrownEnderPearlMeta::new),
|
||||
entry("minecraft:experience_bottle", ThrownExperienceBottleMeta::new),
|
||||
entry("minecraft:potion", ThrownPotionMeta::new),
|
||||
entry("minecraft:trident", ThrownTridentMeta::new),
|
||||
entry("minecraft:trader_llama", TraderLlamaMeta::new),
|
||||
entry("minecraft:tropical_fish", TropicalFishMeta::new),
|
||||
entry("minecraft:turtle", TurtleMeta::new),
|
||||
entry("minecraft:vex", VexMeta::new),
|
||||
entry("minecraft:villager", VillagerMeta::new),
|
||||
entry("minecraft:vindicator", VindicatorMeta::new),
|
||||
entry("minecraft:wandering_trader", WanderingTraderMeta::new),
|
||||
entry("minecraft:warden", WardenMeta::new),
|
||||
entry("minecraft:wind_charge", WindChargeMeta::new),
|
||||
entry("minecraft:witch", WitchMeta::new),
|
||||
entry("minecraft:wither", WitherMeta::new),
|
||||
entry("minecraft:wither_skeleton", WitherSkeletonMeta::new),
|
||||
entry("minecraft:wither_skull", WitherSkullMeta::new),
|
||||
entry("minecraft:wolf", WolfMeta::new),
|
||||
entry("minecraft:zoglin", ZoglinMeta::new),
|
||||
entry("minecraft:zombie", ZombieMeta::new),
|
||||
entry("minecraft:zombie_horse", ZombieHorseMeta::new),
|
||||
entry("minecraft:zombie_villager", ZombieVillagerMeta::new),
|
||||
entry("minecraft:zombified_piglin", ZombifiedPiglinMeta::new),
|
||||
entry("minecraft:player", PlayerMeta::new),
|
||||
entry("minecraft:fishing_bobber", FishingHookMeta::new)
|
||||
Map.entry("minecraft:allay", AllayMeta::new),
|
||||
Map.entry("minecraft:area_effect_cloud", AreaEffectCloudMeta::new),
|
||||
Map.entry("minecraft:armadillo", ArmadilloMeta::new),
|
||||
Map.entry("minecraft:armor_stand", ArmorStandMeta::new),
|
||||
Map.entry("minecraft:arrow", ArrowMeta::new),
|
||||
Map.entry("minecraft:axolotl", AxolotlMeta::new),
|
||||
Map.entry("minecraft:bat", BatMeta::new),
|
||||
Map.entry("minecraft:bee", BeeMeta::new),
|
||||
Map.entry("minecraft:blaze", BlazeMeta::new),
|
||||
Map.entry("minecraft:block_display", BlockDisplayMeta::new),
|
||||
Map.entry("minecraft:boat", BoatMeta::new),
|
||||
Map.entry("minecraft:bogged", BoggedMeta::new),
|
||||
Map.entry("minecraft:breeze", BreezeMeta::new),
|
||||
Map.entry("minecraft:breeze_wind_charge", BreezeWindChargeMeta::new),
|
||||
Map.entry("minecraft:chest_boat", BoatMeta::new),
|
||||
Map.entry("minecraft:camel", CamelMeta::new),
|
||||
Map.entry("minecraft:cat", CatMeta::new),
|
||||
Map.entry("minecraft:cave_spider", CaveSpiderMeta::new),
|
||||
Map.entry("minecraft:chicken", ChickenMeta::new),
|
||||
Map.entry("minecraft:cod", CodMeta::new),
|
||||
Map.entry("minecraft:cow", CowMeta::new),
|
||||
Map.entry("minecraft:creeper", CreeperMeta::new),
|
||||
Map.entry("minecraft:dolphin", DolphinMeta::new),
|
||||
Map.entry("minecraft:donkey", DonkeyMeta::new),
|
||||
Map.entry("minecraft:dragon_fireball", DragonFireballMeta::new),
|
||||
Map.entry("minecraft:drowned", DrownedMeta::new),
|
||||
Map.entry("minecraft:elder_guardian", ElderGuardianMeta::new),
|
||||
Map.entry("minecraft:end_crystal", EndCrystalMeta::new),
|
||||
Map.entry("minecraft:ender_dragon", EnderDragonMeta::new),
|
||||
Map.entry("minecraft:enderman", EndermanMeta::new),
|
||||
Map.entry("minecraft:endermite", EndermiteMeta::new),
|
||||
Map.entry("minecraft:evoker", EvokerMeta::new),
|
||||
Map.entry("minecraft:evoker_fangs", EvokerFangsMeta::new),
|
||||
Map.entry("minecraft:experience_orb", ExperienceOrbMeta::new),
|
||||
Map.entry("minecraft:eye_of_ender", EyeOfEnderMeta::new),
|
||||
Map.entry("minecraft:falling_block", FallingBlockMeta::new),
|
||||
Map.entry("minecraft:firework_rocket", FireworkRocketMeta::new),
|
||||
Map.entry("minecraft:fox", FoxMeta::new),
|
||||
Map.entry("minecraft:frog", FrogMeta::new),
|
||||
Map.entry("minecraft:ghast", GhastMeta::new),
|
||||
Map.entry("minecraft:giant", GiantMeta::new),
|
||||
Map.entry("minecraft:glow_item_frame", GlowItemFrameMeta::new),
|
||||
Map.entry("minecraft:glow_squid", GlowSquidMeta::new),
|
||||
Map.entry("minecraft:goat", GoatMeta::new),
|
||||
Map.entry("minecraft:guardian", GuardianMeta::new),
|
||||
Map.entry("minecraft:hoglin", HoglinMeta::new),
|
||||
Map.entry("minecraft:horse", HorseMeta::new),
|
||||
Map.entry("minecraft:husk", HuskMeta::new),
|
||||
Map.entry("minecraft:illusioner", IllusionerMeta::new),
|
||||
Map.entry("minecraft:interaction", InteractionMeta::new),
|
||||
Map.entry("minecraft:iron_golem", IronGolemMeta::new),
|
||||
Map.entry("minecraft:item", ItemEntityMeta::new),
|
||||
Map.entry("minecraft:item_display", ItemDisplayMeta::new),
|
||||
Map.entry("minecraft:item_frame", ItemFrameMeta::new),
|
||||
Map.entry("minecraft:fireball", FireballMeta::new),
|
||||
Map.entry("minecraft:leash_knot", LeashKnotMeta::new),
|
||||
Map.entry("minecraft:lightning_bolt", LightningBoltMeta::new),
|
||||
Map.entry("minecraft:llama", LlamaMeta::new),
|
||||
Map.entry("minecraft:llama_spit", LlamaSpitMeta::new),
|
||||
Map.entry("minecraft:magma_cube", MagmaCubeMeta::new),
|
||||
Map.entry("minecraft:marker", MarkerMeta::new),
|
||||
Map.entry("minecraft:minecart", MinecartMeta::new),
|
||||
Map.entry("minecraft:chest_minecart", ChestMinecartMeta::new),
|
||||
Map.entry("minecraft:command_block_minecart", CommandBlockMinecartMeta::new),
|
||||
Map.entry("minecraft:furnace_minecart", FurnaceMinecartMeta::new),
|
||||
Map.entry("minecraft:hopper_minecart", HopperMinecartMeta::new),
|
||||
Map.entry("minecraft:spawner_minecart", SpawnerMinecartMeta::new),
|
||||
Map.entry("minecraft:text_display", TextDisplayMeta::new),
|
||||
Map.entry("minecraft:tnt_minecart", TntMinecartMeta::new),
|
||||
Map.entry("minecraft:mule", MuleMeta::new),
|
||||
Map.entry("minecraft:mooshroom", MooshroomMeta::new),
|
||||
Map.entry("minecraft:ocelot", OcelotMeta::new),
|
||||
Map.entry("minecraft:ominous_item_spawner", OminousItemSpawnerMeta::new),
|
||||
Map.entry("minecraft:painting", PaintingMeta::new),
|
||||
Map.entry("minecraft:panda", PandaMeta::new),
|
||||
Map.entry("minecraft:parrot", ParrotMeta::new),
|
||||
Map.entry("minecraft:phantom", PhantomMeta::new),
|
||||
Map.entry("minecraft:pig", PigMeta::new),
|
||||
Map.entry("minecraft:piglin", PiglinMeta::new),
|
||||
Map.entry("minecraft:piglin_brute", PiglinBruteMeta::new),
|
||||
Map.entry("minecraft:pillager", PillagerMeta::new),
|
||||
Map.entry("minecraft:polar_bear", PolarBearMeta::new),
|
||||
Map.entry("minecraft:tnt", PrimedTntMeta::new),
|
||||
Map.entry("minecraft:pufferfish", PufferfishMeta::new),
|
||||
Map.entry("minecraft:rabbit", RabbitMeta::new),
|
||||
Map.entry("minecraft:ravager", RavagerMeta::new),
|
||||
Map.entry("minecraft:salmon", SalmonMeta::new),
|
||||
Map.entry("minecraft:sheep", SheepMeta::new),
|
||||
Map.entry("minecraft:shulker", ShulkerMeta::new),
|
||||
Map.entry("minecraft:shulker_bullet", ShulkerBulletMeta::new),
|
||||
Map.entry("minecraft:silverfish", SilverfishMeta::new),
|
||||
Map.entry("minecraft:skeleton", SkeletonMeta::new),
|
||||
Map.entry("minecraft:skeleton_horse", SkeletonHorseMeta::new),
|
||||
Map.entry("minecraft:slime", SlimeMeta::new),
|
||||
Map.entry("minecraft:small_fireball", SmallFireballMeta::new),
|
||||
Map.entry("minecraft:sniffer", SnifferMeta::new),
|
||||
Map.entry("minecraft:snow_golem", SnowGolemMeta::new),
|
||||
Map.entry("minecraft:snowball", SnowballMeta::new),
|
||||
Map.entry("minecraft:spectral_arrow", SpectralArrowMeta::new),
|
||||
Map.entry("minecraft:spider", SpiderMeta::new),
|
||||
Map.entry("minecraft:squid", SquidMeta::new),
|
||||
Map.entry("minecraft:stray", StrayMeta::new),
|
||||
Map.entry("minecraft:strider", StriderMeta::new),
|
||||
Map.entry("minecraft:tadpole", TadpoleMeta::new),
|
||||
Map.entry("minecraft:egg", ThrownEggMeta::new),
|
||||
Map.entry("minecraft:ender_pearl", ThrownEnderPearlMeta::new),
|
||||
Map.entry("minecraft:experience_bottle", ThrownExperienceBottleMeta::new),
|
||||
Map.entry("minecraft:potion", ThrownPotionMeta::new),
|
||||
Map.entry("minecraft:trident", ThrownTridentMeta::new),
|
||||
Map.entry("minecraft:trader_llama", TraderLlamaMeta::new),
|
||||
Map.entry("minecraft:tropical_fish", TropicalFishMeta::new),
|
||||
Map.entry("minecraft:turtle", TurtleMeta::new),
|
||||
Map.entry("minecraft:vex", VexMeta::new),
|
||||
Map.entry("minecraft:villager", VillagerMeta::new),
|
||||
Map.entry("minecraft:vindicator", VindicatorMeta::new),
|
||||
Map.entry("minecraft:wandering_trader", WanderingTraderMeta::new),
|
||||
Map.entry("minecraft:warden", WardenMeta::new),
|
||||
Map.entry("minecraft:wind_charge", WindChargeMeta::new),
|
||||
Map.entry("minecraft:witch", WitchMeta::new),
|
||||
Map.entry("minecraft:wither", WitherMeta::new),
|
||||
Map.entry("minecraft:wither_skeleton", WitherSkeletonMeta::new),
|
||||
Map.entry("minecraft:wither_skull", WitherSkullMeta::new),
|
||||
Map.entry("minecraft:wolf", WolfMeta::new),
|
||||
Map.entry("minecraft:zoglin", ZoglinMeta::new),
|
||||
Map.entry("minecraft:zombie", ZombieMeta::new),
|
||||
Map.entry("minecraft:zombie_horse", ZombieHorseMeta::new),
|
||||
Map.entry("minecraft:zombie_villager", ZombieVillagerMeta::new),
|
||||
Map.entry("minecraft:zombified_piglin", ZombifiedPiglinMeta::new),
|
||||
Map.entry("minecraft:player", PlayerMeta::new),
|
||||
Map.entry("minecraft:fishing_bobber", FishingHookMeta::new)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
package net.minestom.server.entity;
|
||||
|
||||
import net.minestom.server.item.attribute.AttributeSlot;
|
||||
import net.minestom.server.utils.inventory.PlayerInventoryUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static net.minestom.server.utils.inventory.PlayerInventoryUtils.*;
|
||||
|
||||
public enum EquipmentSlot {
|
||||
MAIN_HAND(false, -1),
|
||||
OFF_HAND(false, -1),
|
||||
BOOTS(true, BOOTS_SLOT),
|
||||
LEGGINGS(true, LEGGINGS_SLOT),
|
||||
CHESTPLATE(true, CHESTPLATE_SLOT),
|
||||
HELMET(true, HELMET_SLOT);
|
||||
BOOTS(true, PlayerInventoryUtils.BOOTS_SLOT),
|
||||
LEGGINGS(true, PlayerInventoryUtils.LEGGINGS_SLOT),
|
||||
CHESTPLATE(true, PlayerInventoryUtils.CHESTPLATE_SLOT),
|
||||
HELMET(true, PlayerInventoryUtils.HELMET_SLOT);
|
||||
|
||||
private static final List<EquipmentSlot> ARMORS = List.of(BOOTS, LEGGINGS, CHESTPLATE, HELMET);
|
||||
|
||||
|
@ -41,14 +39,4 @@ public enum EquipmentSlot {
|
|||
return ARMORS;
|
||||
}
|
||||
|
||||
public static @NotNull EquipmentSlot fromAttributeSlot(@NotNull AttributeSlot attributeSlot) {
|
||||
return switch (attributeSlot) {
|
||||
case MAINHAND -> MAIN_HAND;
|
||||
case OFFHAND -> OFF_HAND;
|
||||
case FEET -> BOOTS;
|
||||
case LEGS -> LEGGINGS;
|
||||
case CHEST -> CHESTPLATE;
|
||||
case HEAD -> HELMET;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import net.minestom.server.event.EventDispatcher;
|
|||
import net.minestom.server.event.entity.EntityItemMergeEvent;
|
||||
import net.minestom.server.instance.EntityTracker;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.item.StackingRule;
|
||||
import net.minestom.server.utils.MathUtils;
|
||||
import net.minestom.server.utils.time.Cooldown;
|
||||
import net.minestom.server.utils.time.TimeUnit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
@ -77,13 +77,12 @@ public class ItemEntity extends Entity {
|
|||
if (getDistanceSquared(itemEntity) > mergeRange * mergeRange) return;
|
||||
|
||||
final ItemStack itemStackEntity = itemEntity.getItemStack();
|
||||
final StackingRule stackingRule = StackingRule.get();
|
||||
final boolean canStack = stackingRule.canBeStacked(itemStack, itemStackEntity);
|
||||
final boolean canStack = itemStack.isSimilar(itemStackEntity);
|
||||
|
||||
if (!canStack) return;
|
||||
final int totalAmount = stackingRule.getAmount(itemStack) + stackingRule.getAmount(itemStackEntity);
|
||||
if (!stackingRule.canApply(itemStack, totalAmount)) return;
|
||||
final ItemStack result = stackingRule.apply(itemStack, totalAmount);
|
||||
final int totalAmount = itemStack.amount() + itemStackEntity.amount();
|
||||
if (!MathUtils.isBetween(totalAmount, 0, itemStack.maxStackSize())) return;
|
||||
final ItemStack result = itemStack.withAmount(totalAmount);
|
||||
EntityItemMergeEvent entityItemMergeEvent = new EntityItemMergeEvent(this, itemEntity, result);
|
||||
EventDispatcher.callCancellable(entityItemMergeEvent, () -> {
|
||||
setItemStack(entityItemMergeEvent.getResult());
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package net.minestom.server.entity;
|
||||
|
||||
import net.kyori.adventure.sound.Sound.Source;
|
||||
import net.minestom.server.attribute.Attribute;
|
||||
import net.minestom.server.attribute.AttributeInstance;
|
||||
import net.minestom.server.collision.BoundingBox;
|
||||
import net.minestom.server.coordinate.Point;
|
||||
import net.minestom.server.coordinate.Vec;
|
||||
import net.minestom.server.entity.attribute.Attribute;
|
||||
import net.minestom.server.entity.attribute.AttributeInstance;
|
||||
import net.minestom.server.entity.damage.Damage;
|
||||
import net.minestom.server.entity.damage.DamageType;
|
||||
import net.minestom.server.entity.metadata.LivingEntityMeta;
|
||||
|
@ -22,7 +22,7 @@ import net.minestom.server.network.ConnectionState;
|
|||
import net.minestom.server.network.packet.server.LazyPacket;
|
||||
import net.minestom.server.network.packet.server.play.CollectItemPacket;
|
||||
import net.minestom.server.network.packet.server.play.EntityAnimationPacket;
|
||||
import net.minestom.server.network.packet.server.play.EntityPropertiesPacket;
|
||||
import net.minestom.server.network.packet.server.play.EntityAttributesPacket;
|
||||
import net.minestom.server.network.packet.server.play.SoundEffectPacket;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.scoreboard.Team;
|
||||
|
@ -366,8 +366,7 @@ public class LivingEntity extends Entity implements EquipmentHandler {
|
|||
// TODO: separate living entity categories
|
||||
soundCategory = Source.HOSTILE;
|
||||
}
|
||||
sendPacketToViewersAndSelf(new SoundEffectPacket(sound, null, soundCategory,
|
||||
getPosition(), 1.0f, 1.0f, 0));
|
||||
sendPacketToViewersAndSelf(new SoundEffectPacket(sound, soundCategory, getPosition(), 1.0f, 1.0f, 0));
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -399,7 +398,7 @@ public class LivingEntity extends Entity implements EquipmentHandler {
|
|||
* @param health the new entity health
|
||||
*/
|
||||
public void setHealth(float health) {
|
||||
this.health = Math.min(health, getMaxHealth());
|
||||
this.health = Math.min(health, (float) getAttributeValue(Attribute.GENERIC_MAX_HEALTH));
|
||||
if (this.health <= 0 && !isDead) {
|
||||
kill();
|
||||
}
|
||||
|
@ -418,22 +417,13 @@ public class LivingEntity extends Entity implements EquipmentHandler {
|
|||
return lastDamage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the entity max health from {@link #getAttributeValue(Attribute)} {@link Attribute#MAX_HEALTH}.
|
||||
*
|
||||
* @return the entity max health
|
||||
*/
|
||||
public float getMaxHealth() {
|
||||
return getAttributeValue(Attribute.MAX_HEALTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the heal of the entity as its max health.
|
||||
* <p>
|
||||
* Retrieved from {@link #getAttributeValue(Attribute)} with the attribute {@link Attribute#MAX_HEALTH}.
|
||||
* Retrieved from {@link #getAttributeValue(Attribute)} with the attribute {@link Attribute#GENERIC_MAX_HEALTH}.
|
||||
*/
|
||||
public void heal() {
|
||||
setHealth(getAttributeValue(Attribute.MAX_HEALTH));
|
||||
setHealth((float) getAttributeValue(Attribute.GENERIC_MAX_HEALTH));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -443,7 +433,7 @@ public class LivingEntity extends Entity implements EquipmentHandler {
|
|||
* @return the attribute instance
|
||||
*/
|
||||
public @NotNull AttributeInstance getAttribute(@NotNull Attribute attribute) {
|
||||
return attributeModifiers.computeIfAbsent(attribute.key(),
|
||||
return attributeModifiers.computeIfAbsent(attribute.name(),
|
||||
s -> new AttributeInstance(attribute, this::onAttributeChanged));
|
||||
}
|
||||
|
||||
|
@ -459,7 +449,7 @@ public class LivingEntity extends Entity implements EquipmentHandler {
|
|||
// connection null during Player initialization (due to #super call)
|
||||
self = playerConnection != null && playerConnection.getConnectionState() == ConnectionState.PLAY;
|
||||
}
|
||||
EntityPropertiesPacket propertiesPacket = new EntityPropertiesPacket(getEntityId(), List.of(attributeInstance));
|
||||
EntityAttributesPacket propertiesPacket = new EntityAttributesPacket(getEntityId(), List.of(attributeInstance));
|
||||
if (self) {
|
||||
sendPacketToViewersAndSelf(propertiesPacket);
|
||||
} else {
|
||||
|
@ -473,8 +463,8 @@ public class LivingEntity extends Entity implements EquipmentHandler {
|
|||
* @param attribute the attribute value to get
|
||||
* @return the attribute value
|
||||
*/
|
||||
public float getAttributeValue(@NotNull Attribute attribute) {
|
||||
AttributeInstance instance = attributeModifiers.get(attribute.key());
|
||||
public double getAttributeValue(@NotNull Attribute attribute) {
|
||||
AttributeInstance instance = attributeModifiers.get(attribute.name());
|
||||
return (instance != null) ? instance.getValue() : attribute.defaultValue();
|
||||
}
|
||||
|
||||
|
@ -566,12 +556,12 @@ public class LivingEntity extends Entity implements EquipmentHandler {
|
|||
}
|
||||
|
||||
/**
|
||||
* Gets an {@link EntityPropertiesPacket} for this entity with all of its attributes values.
|
||||
* Gets an {@link EntityAttributesPacket} for this entity with all of its attributes values.
|
||||
*
|
||||
* @return an {@link EntityPropertiesPacket} linked to this entity
|
||||
* @return an {@link EntityAttributesPacket} linked to this entity
|
||||
*/
|
||||
protected @NotNull EntityPropertiesPacket getPropertiesPacket() {
|
||||
return new EntityPropertiesPacket(getEntityId(), List.copyOf(attributeModifiers.values()));
|
||||
protected @NotNull EntityAttributesPacket getPropertiesPacket() {
|
||||
return new EntityAttributesPacket(getEntityId(), List.copyOf(attributeModifiers.values()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -667,7 +657,7 @@ public class LivingEntity extends Entity implements EquipmentHandler {
|
|||
*/
|
||||
@Override
|
||||
public void takeKnockback(float strength, final double x, final double z) {
|
||||
strength *= 1 - getAttributeValue(Attribute.KNOCKBACK_RESISTANCE);
|
||||
strength *= (float) (1 - getAttributeValue(Attribute.GENERIC_KNOCKBACK_RESISTANCE));
|
||||
super.takeKnockback(strength, x, z);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package net.minestom.server.entity;
|
||||
|
||||
import net.kyori.adventure.nbt.BinaryTag;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.minestom.server.coordinate.Point;
|
||||
import net.minestom.server.entity.metadata.animal.ArmadilloMeta;
|
||||
import net.minestom.server.entity.metadata.animal.FrogMeta;
|
||||
import net.minestom.server.entity.metadata.animal.SnifferMeta;
|
||||
import net.minestom.server.entity.metadata.animal.tameable.CatMeta;
|
||||
|
@ -15,14 +17,11 @@ import org.jetbrains.annotations.ApiStatus;
|
|||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.UnknownNullability;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBT;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.VarHandle;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public final class Metadata {
|
||||
public static Entry<Byte> Byte(byte value) {
|
||||
|
@ -50,11 +49,11 @@ public final class Metadata {
|
|||
}
|
||||
|
||||
public static Entry<Component> OptChat(@Nullable Component value) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_OPTCHAT, value, NetworkBuffer.OPT_CHAT);
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_OPT_CHAT, value, NetworkBuffer.OPT_CHAT);
|
||||
}
|
||||
|
||||
public static Entry<ItemStack> Slot(@NotNull ItemStack value) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_SLOT, value, NetworkBuffer.ITEM);
|
||||
public static Entry<ItemStack> ItemStack(@NotNull ItemStack value) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_ITEM_STACK, value, ItemStack.NETWORK_TYPE);
|
||||
}
|
||||
|
||||
public static Entry<Boolean> Boolean(boolean value) {
|
||||
|
@ -65,12 +64,12 @@ public final class Metadata {
|
|||
return new MetadataImpl.EntryImpl<>(TYPE_ROTATION, value, NetworkBuffer.VECTOR3);
|
||||
}
|
||||
|
||||
public static Entry<Point> Position(@NotNull Point value) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_POSITION, value, NetworkBuffer.BLOCK_POSITION);
|
||||
public static Entry<Point> BlockPosition(@NotNull Point value) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_BLOCK_POSITION, value, NetworkBuffer.BLOCK_POSITION);
|
||||
}
|
||||
|
||||
public static Entry<Point> OptPosition(@Nullable Point value) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_OPTPOSITION, value, NetworkBuffer.OPT_BLOCK_POSITION);
|
||||
public static Entry<Point> OptBlockPosition(@Nullable Point value) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_OPT_BLOCK_POSITION, value, NetworkBuffer.OPT_BLOCK_POSITION);
|
||||
}
|
||||
|
||||
public static Entry<Direction> Direction(@NotNull Direction value) {
|
||||
|
@ -78,7 +77,7 @@ public final class Metadata {
|
|||
}
|
||||
|
||||
public static Entry<UUID> OptUUID(@Nullable UUID value) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_OPTUUID, value, NetworkBuffer.OPT_UUID);
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_OPT_UUID, value, NetworkBuffer.OPT_UUID);
|
||||
}
|
||||
|
||||
public static Entry<Integer> BlockState(@Nullable Integer value) {
|
||||
|
@ -86,22 +85,49 @@ public final class Metadata {
|
|||
}
|
||||
|
||||
public static Entry<Integer> OptBlockState(@Nullable Integer value) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_OPTBLOCKSTATE, value, NetworkBuffer.OPT_BLOCK_STATE);
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_OPT_BLOCKSTATE, value, NetworkBuffer.OPT_BLOCK_STATE);
|
||||
}
|
||||
|
||||
public static Entry<NBT> NBT(@NotNull NBT nbt) {
|
||||
public static Entry<BinaryTag> NBT(@NotNull BinaryTag nbt) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_NBT, nbt, NetworkBuffer.NBT);
|
||||
}
|
||||
|
||||
public static Entry<int[]> VillagerData(int villagerType,
|
||||
int villagerProfession,
|
||||
int level) {
|
||||
public static Entry<Particle> Particle(@NotNull Particle particle) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_PARTICLE, particle, NetworkBuffer.PARTICLE);
|
||||
}
|
||||
|
||||
public static Entry<List<Particle>> ParticleList(@NotNull List<Particle> particles) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_PARTICLE_LIST, particles, new NetworkBuffer.Type<>() {
|
||||
@Override
|
||||
public void write(@NotNull NetworkBuffer buffer, List<Particle> value) {
|
||||
buffer.writeCollection(NetworkBuffer.PARTICLE, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Particle> read(@NotNull NetworkBuffer buffer) {
|
||||
return buffer.readCollection(NetworkBuffer.PARTICLE, Integer.MAX_VALUE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static Entry<int[]> VillagerData(int villagerType, int villagerProfession, int level) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_VILLAGERDATA, new int[]{villagerType, villagerProfession, level},
|
||||
NetworkBuffer.VILLAGER_DATA);
|
||||
}
|
||||
|
||||
public static Entry<Integer> OptVarInt(@Nullable Integer value) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_OPTVARINT, value, NetworkBuffer.OPT_VAR_INT);
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_OPT_VARINT, value, new NetworkBuffer.Type<>() {
|
||||
@Override
|
||||
public void write(@NotNull NetworkBuffer buffer, Integer value) {
|
||||
buffer.write(NetworkBuffer.VAR_INT, value == null ? 0 : value + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer read(@NotNull NetworkBuffer buffer) {
|
||||
int value = buffer.read(NetworkBuffer.VAR_INT);
|
||||
return value == 0 ? null : value - 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static Entry<Entity.Pose> Pose(@NotNull Entity.Pose value) {
|
||||
|
@ -112,18 +138,24 @@ public final class Metadata {
|
|||
return new MetadataImpl.EntryImpl<>(TYPE_CAT_VARIANT, value, NetworkBuffer.CAT_VARIANT);
|
||||
}
|
||||
|
||||
// WOLF VARIANT
|
||||
|
||||
public static Entry<FrogMeta.Variant> FrogVariant(@NotNull FrogMeta.Variant value) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_FROG_VARIANT, value, NetworkBuffer.FROG_VARIANT);
|
||||
}
|
||||
|
||||
public static Entry<PaintingMeta.Variant> PaintingVariant(@NotNull PaintingMeta.Variant value) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_PAINTINGVARIANT, value, NetworkBuffer.PAINTING_VARIANT);
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_PAINTING_VARIANT, value, NetworkBuffer.PAINTING_VARIANT);
|
||||
}
|
||||
|
||||
public static Entry<SnifferMeta.State> SnifferState(@NotNull SnifferMeta.State value) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_SNIFFER_STATE, value, NetworkBuffer.SNIFFER_STATE);
|
||||
}
|
||||
|
||||
public static Entry<ArmadilloMeta.State> ArmadilloState(@NotNull ArmadilloMeta.State value) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_ARMADILLO_STATE, value, NetworkBuffer.ARMADILLO_STATE);
|
||||
}
|
||||
|
||||
public static Entry<Point> Vector3(@NotNull Point value) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_VECTOR3, value, NetworkBuffer.VECTOR3);
|
||||
}
|
||||
|
@ -132,41 +164,46 @@ public final class Metadata {
|
|||
return new MetadataImpl.EntryImpl<>(TYPE_QUATERNION, value, NetworkBuffer.QUATERNION);
|
||||
}
|
||||
|
||||
public static Entry<Particle> Particle(@NotNull Particle particle) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_PARTICLE, particle, NetworkBuffer.PARTICLE);
|
||||
}
|
||||
private static final AtomicInteger NEXT_ID = new AtomicInteger(0);
|
||||
|
||||
public static final byte TYPE_BYTE = 0;
|
||||
public static final byte TYPE_VARINT = 1;
|
||||
public static final byte TYPE_LONG = 2;
|
||||
public static final byte TYPE_FLOAT = 3;
|
||||
public static final byte TYPE_STRING = 4;
|
||||
public static final byte TYPE_CHAT = 5;
|
||||
public static final byte TYPE_OPTCHAT = 6;
|
||||
public static final byte TYPE_SLOT = 7;
|
||||
public static final byte TYPE_BOOLEAN = 8;
|
||||
public static final byte TYPE_ROTATION = 9;
|
||||
public static final byte TYPE_POSITION = 10;
|
||||
public static final byte TYPE_OPTPOSITION = 11;
|
||||
public static final byte TYPE_DIRECTION = 12;
|
||||
public static final byte TYPE_OPTUUID = 13;
|
||||
public static final byte TYPE_BLOCKSTATE = 14;
|
||||
public static final byte TYPE_OPTBLOCKSTATE = 15;
|
||||
public static final byte TYPE_NBT = 16;
|
||||
public static final byte TYPE_PARTICLE = 17;
|
||||
public static final byte TYPE_VILLAGERDATA = 18;
|
||||
public static final byte TYPE_OPTVARINT = 19;
|
||||
public static final byte TYPE_POSE = 20;
|
||||
public static final byte TYPE_CAT_VARIANT = 21;
|
||||
public static final byte TYPE_FROG_VARIANT = 22;
|
||||
public static final byte TYPE_OPTGLOBALPOS = 23;
|
||||
public static final byte TYPE_PAINTINGVARIANT = 24;
|
||||
public static final byte TYPE_SNIFFER_STATE = 25;
|
||||
public static final byte TYPE_VECTOR3 = 26;
|
||||
public static final byte TYPE_QUATERNION = 27;
|
||||
public static final byte TYPE_BYTE = nextId();
|
||||
public static final byte TYPE_VARINT = nextId();
|
||||
public static final byte TYPE_LONG = nextId();
|
||||
public static final byte TYPE_FLOAT = nextId();
|
||||
public static final byte TYPE_STRING = nextId();
|
||||
public static final byte TYPE_CHAT = nextId();
|
||||
public static final byte TYPE_OPT_CHAT = nextId();
|
||||
public static final byte TYPE_ITEM_STACK = nextId();
|
||||
public static final byte TYPE_BOOLEAN = nextId();
|
||||
public static final byte TYPE_ROTATION = nextId();
|
||||
public static final byte TYPE_BLOCK_POSITION = nextId();
|
||||
public static final byte TYPE_OPT_BLOCK_POSITION = nextId();
|
||||
public static final byte TYPE_DIRECTION = nextId();
|
||||
public static final byte TYPE_OPT_UUID = nextId();
|
||||
public static final byte TYPE_BLOCKSTATE = nextId();
|
||||
public static final byte TYPE_OPT_BLOCKSTATE = nextId();
|
||||
public static final byte TYPE_NBT = nextId();
|
||||
public static final byte TYPE_PARTICLE = nextId();
|
||||
public static final byte TYPE_PARTICLE_LIST = nextId();
|
||||
public static final byte TYPE_VILLAGERDATA = nextId();
|
||||
public static final byte TYPE_OPT_VARINT = nextId();
|
||||
public static final byte TYPE_POSE = nextId();
|
||||
public static final byte TYPE_CAT_VARIANT = nextId();
|
||||
public static final byte TYPE_WOLF_VARIANT = nextId();
|
||||
public static final byte TYPE_FROG_VARIANT = nextId();
|
||||
public static final byte TYPE_OPT_GLOBAL_POSITION = nextId(); // Unused by protocol it seems
|
||||
public static final byte TYPE_PAINTING_VARIANT = nextId();
|
||||
public static final byte TYPE_SNIFFER_STATE = nextId();
|
||||
public static final byte TYPE_ARMADILLO_STATE = nextId();
|
||||
public static final byte TYPE_VECTOR3 = nextId();
|
||||
public static final byte TYPE_QUATERNION = nextId();
|
||||
|
||||
// Impl Note: Adding an entry here requires that a default value entry is added in MetadataImpl.EMPTY_VALUES
|
||||
|
||||
private static byte nextId() {
|
||||
return (byte) NEXT_ID.getAndIncrement();
|
||||
}
|
||||
|
||||
private static final VarHandle NOTIFIED_CHANGES;
|
||||
|
||||
static {
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package net.minestom.server.entity;
|
||||
|
||||
import net.kyori.adventure.nbt.EndBinaryTag;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.minestom.server.coordinate.Vec;
|
||||
import net.minestom.server.entity.metadata.animal.ArmadilloMeta;
|
||||
import net.minestom.server.entity.metadata.animal.FrogMeta;
|
||||
import net.minestom.server.entity.metadata.animal.SnifferMeta;
|
||||
import net.minestom.server.entity.metadata.animal.tameable.CatMeta;
|
||||
|
@ -9,11 +11,13 @@ import net.minestom.server.entity.metadata.other.PaintingMeta;
|
|||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.network.NetworkBuffer;
|
||||
import net.minestom.server.particle.Particle;
|
||||
import net.minestom.server.utils.Direction;
|
||||
import net.minestom.server.utils.collection.ObjectArray;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.UnknownNullability;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTEnd;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static net.minestom.server.entity.Metadata.*;
|
||||
import static net.minestom.server.network.NetworkBuffer.VAR_INT;
|
||||
|
@ -28,26 +32,29 @@ final class MetadataImpl {
|
|||
EMPTY_VALUES.set(TYPE_FLOAT, Float(0f));
|
||||
EMPTY_VALUES.set(TYPE_STRING, String(""));
|
||||
EMPTY_VALUES.set(TYPE_CHAT, Chat(Component.empty()));
|
||||
EMPTY_VALUES.set(TYPE_OPTCHAT, OptChat(null));
|
||||
EMPTY_VALUES.set(TYPE_SLOT, Slot(ItemStack.AIR));
|
||||
EMPTY_VALUES.set(TYPE_OPT_CHAT, OptChat(null));
|
||||
EMPTY_VALUES.set(TYPE_ITEM_STACK, ItemStack(ItemStack.AIR));
|
||||
EMPTY_VALUES.set(TYPE_BOOLEAN, Boolean(false));
|
||||
EMPTY_VALUES.set(TYPE_ROTATION, Rotation(Vec.ZERO));
|
||||
EMPTY_VALUES.set(TYPE_POSITION, Position(Vec.ZERO));
|
||||
EMPTY_VALUES.set(TYPE_OPTPOSITION, OptPosition(null));
|
||||
EMPTY_VALUES.set(TYPE_BLOCK_POSITION, BlockPosition(Vec.ZERO));
|
||||
EMPTY_VALUES.set(TYPE_OPT_BLOCK_POSITION, OptBlockPosition(null));
|
||||
EMPTY_VALUES.set(TYPE_DIRECTION, Direction(Direction.DOWN));
|
||||
EMPTY_VALUES.set(TYPE_OPTUUID, OptUUID(null));
|
||||
EMPTY_VALUES.set(TYPE_OPT_UUID, OptUUID(null));
|
||||
EMPTY_VALUES.set(TYPE_BLOCKSTATE, BlockState(Block.AIR.id()));
|
||||
EMPTY_VALUES.set(TYPE_OPTBLOCKSTATE, OptBlockState(null));
|
||||
EMPTY_VALUES.set(TYPE_NBT, NBT(NBTEnd.INSTANCE));
|
||||
//EMPTY_VALUES.set(TYPE_PARTICLE -> throw new UnsupportedOperationException();
|
||||
EMPTY_VALUES.set(TYPE_OPT_BLOCKSTATE, OptBlockState(null));
|
||||
EMPTY_VALUES.set(TYPE_NBT, NBT(EndBinaryTag.endBinaryTag()));
|
||||
EMPTY_VALUES.set(TYPE_PARTICLE, Particle(Particle.DUST));
|
||||
EMPTY_VALUES.set(TYPE_PARTICLE_LIST, ParticleList(List.of()));
|
||||
EMPTY_VALUES.set(TYPE_VILLAGERDATA, VillagerData(0, 0, 0));
|
||||
EMPTY_VALUES.set(TYPE_OPTVARINT, OptVarInt(null));
|
||||
EMPTY_VALUES.set(TYPE_OPT_VARINT, OptVarInt(null));
|
||||
EMPTY_VALUES.set(TYPE_POSE, Pose(Entity.Pose.STANDING));
|
||||
EMPTY_VALUES.set(TYPE_CAT_VARIANT, CatVariant(CatMeta.Variant.TABBY));
|
||||
// WolfVariant
|
||||
EMPTY_VALUES.set(TYPE_FROG_VARIANT, FrogVariant(FrogMeta.Variant.TEMPERATE));
|
||||
// OptGlobalPos
|
||||
EMPTY_VALUES.set(TYPE_PAINTINGVARIANT, PaintingVariant(PaintingMeta.Variant.KEBAB));
|
||||
EMPTY_VALUES.set(TYPE_PAINTING_VARIANT, PaintingVariant(PaintingMeta.Variant.KEBAB));
|
||||
EMPTY_VALUES.set(TYPE_SNIFFER_STATE, SnifferState(SnifferMeta.State.IDLING));
|
||||
EMPTY_VALUES.set(TYPE_ARMADILLO_STATE, ArmadilloState(ArmadilloMeta.State.IDLE));
|
||||
EMPTY_VALUES.set(TYPE_VECTOR3, Vector3(Vec.ZERO));
|
||||
EMPTY_VALUES.set(TYPE_QUATERNION, Quaternion(new float[]{0, 0, 0, 0}));
|
||||
EMPTY_VALUES.trim();
|
||||
|
|
|
@ -18,6 +18,7 @@ import net.kyori.adventure.text.event.HoverEvent;
|
|||
import net.kyori.adventure.text.event.HoverEvent.ShowEntity;
|
||||
import net.kyori.adventure.text.event.HoverEventSource;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
import net.kyori.adventure.title.TitlePart;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.ServerFlag;
|
||||
|
@ -25,13 +26,13 @@ import net.minestom.server.advancements.AdvancementTab;
|
|||
import net.minestom.server.adventure.AdventurePacketConvertor;
|
||||
import net.minestom.server.adventure.Localizable;
|
||||
import net.minestom.server.adventure.audience.Audiences;
|
||||
import net.minestom.server.attribute.Attribute;
|
||||
import net.minestom.server.collision.BoundingBox;
|
||||
import net.minestom.server.command.CommandSender;
|
||||
import net.minestom.server.coordinate.Point;
|
||||
import net.minestom.server.coordinate.Pos;
|
||||
import net.minestom.server.coordinate.Vec;
|
||||
import net.minestom.server.effects.Effects;
|
||||
import net.minestom.server.entity.attribute.Attribute;
|
||||
import net.minestom.server.entity.damage.DamageType;
|
||||
import net.minestom.server.entity.metadata.LivingEntityMeta;
|
||||
import net.minestom.server.entity.metadata.PlayerMeta;
|
||||
|
@ -48,9 +49,11 @@ import net.minestom.server.instance.Instance;
|
|||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.inventory.Inventory;
|
||||
import net.minestom.server.inventory.PlayerInventory;
|
||||
import net.minestom.server.inventory.click.Click;
|
||||
import net.minestom.server.item.ItemComponent;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.item.Material;
|
||||
import net.minestom.server.item.metadata.WrittenBookMeta;
|
||||
import net.minestom.server.item.component.WrittenBookContent;
|
||||
import net.minestom.server.listener.manager.PacketListenerManager;
|
||||
import net.minestom.server.message.ChatMessageType;
|
||||
import net.minestom.server.message.ChatPosition;
|
||||
|
@ -178,6 +181,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||
private int level;
|
||||
private int portalCooldown = 0;
|
||||
|
||||
protected Click.Preprocessor clickPreprocessor = new Click.Preprocessor();
|
||||
protected PlayerInventory inventory;
|
||||
private Inventory openInventory;
|
||||
// Used internally to allow the closing of inventory within the inventory listener
|
||||
|
@ -239,7 +243,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||
setRespawnPoint(Pos.ZERO);
|
||||
|
||||
this.settings = new PlayerSettings();
|
||||
this.inventory = new PlayerInventory(this);
|
||||
this.inventory = new PlayerInventory();
|
||||
|
||||
setCanPickupItem(true); // By default
|
||||
|
||||
|
@ -249,7 +253,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||
this.gameMode = GameMode.SURVIVAL;
|
||||
this.dimensionType = DimensionType.OVERWORLD; // Default dimension
|
||||
this.levelFlat = true;
|
||||
getAttribute(Attribute.MOVEMENT_SPEED).setBaseValue(0.1f);
|
||||
getAttribute(Attribute.GENERIC_MOVEMENT_SPEED).setBaseValue(0.1);
|
||||
|
||||
// FakePlayer init its connection there
|
||||
playerConnectionInit();
|
||||
|
@ -291,8 +295,8 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||
final JoinGamePacket joinGamePacket = new JoinGamePacket(
|
||||
getEntityId(), this.hardcore, List.of(), 0,
|
||||
ServerFlag.CHUNK_VIEW_DISTANCE, ServerFlag.CHUNK_VIEW_DISTANCE,
|
||||
false, true, false, dimensionType.toString(), spawnInstance.getDimensionName(),
|
||||
0, gameMode, null, false, levelFlat, deathLocation, portalCooldown);
|
||||
false, true, false, dimensionType.getId(), spawnInstance.getDimensionName(),
|
||||
0, gameMode, null, false, levelFlat, deathLocation, portalCooldown, true);
|
||||
sendPacket(joinGamePacket);
|
||||
|
||||
// Difficulty
|
||||
|
@ -350,7 +354,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||
for (Recipe recipe : recipeManager.getRecipes()) {
|
||||
if (!recipe.shouldShow(this))
|
||||
continue;
|
||||
recipesIdentifier.add(recipe.getRecipeId());
|
||||
recipesIdentifier.add(recipe.id());
|
||||
}
|
||||
if (!recipesIdentifier.isEmpty()) {
|
||||
UnlockRecipesPacket unlockRecipesPacket = new UnlockRecipesPacket(0,
|
||||
|
@ -370,6 +374,8 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||
refreshHealth(); // Heal and send health packet
|
||||
refreshAbilities(); // Send abilities packet
|
||||
|
||||
inventory.addViewer(this);
|
||||
|
||||
return setInstance(spawnInstance);
|
||||
}
|
||||
|
||||
|
@ -441,7 +447,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||
refreshActiveHand(false, isOffHand, false);
|
||||
|
||||
final ItemStack foodItem = itemUpdateStateEvent.getItemStack();
|
||||
final boolean isFood = foodItem.material().isFood();
|
||||
final boolean isFood = foodItem.has(ItemComponent.FOOD);
|
||||
|
||||
if (isFood) {
|
||||
PlayerEatEvent playerEatEvent = new PlayerEatEvent(this, foodItem, eatingHand);
|
||||
|
@ -519,7 +525,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||
setOnFire(false);
|
||||
refreshHealth();
|
||||
|
||||
sendPacket(new RespawnPacket(getDimensionType().toString(), instance.getDimensionName(),
|
||||
sendPacket(new RespawnPacket(getDimensionType().getId(), instance.getDimensionName(),
|
||||
0, gameMode, gameMode, false, levelFlat, deathLocation, portalCooldown, RespawnPacket.COPY_ALL));
|
||||
refreshClientStateAfterRespawn();
|
||||
|
||||
|
@ -738,6 +744,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||
|
||||
// Load the nearby chunks and queue them to be sent to them
|
||||
ChunkUtils.forChunksInRange(spawnPosition, settings.getEffectiveViewDistance(), chunkAdder);
|
||||
sendPendingChunks(); // Send available first chunk immediately to prevent falling through the floor
|
||||
}
|
||||
|
||||
synchronizePositionAfterTeleport(spawnPosition, 0); // So the player doesn't get stuck
|
||||
|
@ -1000,19 +1007,19 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||
closeInventory();
|
||||
}
|
||||
|
||||
// TODO: when adventure updates, delete this
|
||||
String title = PlainTextComponentSerializer.plainText().serialize(book.title());
|
||||
String author = PlainTextComponentSerializer.plainText().serialize(book.author());
|
||||
final ItemStack writtenBook = ItemStack.builder(Material.WRITTEN_BOOK)
|
||||
.meta(WrittenBookMeta.class, builder -> builder.resolved(false)
|
||||
.generation(WrittenBookMeta.WrittenBookGeneration.ORIGINAL)
|
||||
.author(book.author())
|
||||
.title(book.title())
|
||||
.pages(book.pages()))
|
||||
.set(ItemComponent.WRITTEN_BOOK_CONTENT, new WrittenBookContent(book.pages(), title, author, 0, false))
|
||||
.build();
|
||||
|
||||
// Set book in offhand
|
||||
sendPacket(new SetSlotPacket((byte) 0, 0, (short) PlayerInventoryUtils.OFFHAND_SLOT, writtenBook));
|
||||
sendPacket(new SetSlotPacket((byte) 0, 0, (short) PlayerInventoryUtils.OFF_HAND_SLOT, writtenBook));
|
||||
// Open the book
|
||||
sendPacket(new OpenBookPacket(Hand.OFF));
|
||||
// Restore the item in offhand
|
||||
sendPacket(new SetSlotPacket((byte) 0, 0, (short) PlayerInventoryUtils.OFFHAND_SLOT, getItemInOffHand()));
|
||||
sendPacket(new SetSlotPacket((byte) 0, 0, (short) PlayerInventoryUtils.OFF_HAND_SLOT, getItemInOffHand()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1091,14 +1098,14 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets and refresh client food saturation.
|
||||
* Sets and refresh client food saturationModifier.
|
||||
*
|
||||
* @param foodSaturation the food saturation
|
||||
* @param foodSaturation the food saturationModifier
|
||||
* @throws IllegalArgumentException if {@code foodSaturation} is not between 0 and 20
|
||||
*/
|
||||
public void setFoodSaturation(float foodSaturation) {
|
||||
Check.argCondition(!MathUtils.isBetween(foodSaturation, 0, 20),
|
||||
"Food saturation has to be between 0 and 20");
|
||||
"Food saturationModifier has to be between 0 and 20");
|
||||
this.foodSaturation = foodSaturation;
|
||||
sendPacket(new UpdateHealthPacket(getHealth(), food, foodSaturation));
|
||||
}
|
||||
|
@ -1198,7 +1205,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||
final PlayerInfoRemovePacket removePlayerPacket = getRemovePlayerToList();
|
||||
final PlayerInfoUpdatePacket addPlayerPacket = getAddPlayerToList();
|
||||
|
||||
RespawnPacket respawnPacket = new RespawnPacket(getDimensionType().toString(), instance.getDimensionName(),
|
||||
RespawnPacket respawnPacket = new RespawnPacket(getDimensionType().getId(), instance.getDimensionName(),
|
||||
0, gameMode, gameMode, false, levelFlat, deathLocation, portalCooldown, RespawnPacket.COPY_ALL);
|
||||
|
||||
sendPacket(removePlayerPacket);
|
||||
|
@ -1649,7 +1656,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||
Check.argCondition(instance.getDimensionName().equals(dimensionName),
|
||||
"The dimension needs to be different than the current one!");
|
||||
this.dimensionType = dimensionType;
|
||||
sendPacket(new RespawnPacket(dimensionType.toString(), dimensionName,
|
||||
sendPacket(new RespawnPacket(dimensionType.getId(), dimensionName,
|
||||
0, gameMode, gameMode, false, levelFlat,
|
||||
deathLocation, portalCooldown, RespawnPacket.COPY_ALL));
|
||||
refreshClientStateAfterRespawn();
|
||||
|
@ -1717,6 +1724,10 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||
this.belowNameTag = belowNameTag;
|
||||
}
|
||||
|
||||
public @NotNull Click.Preprocessor clickPreprocessor() {
|
||||
return clickPreprocessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player open inventory.
|
||||
*
|
||||
|
@ -1726,6 +1737,19 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||
return openInventory;
|
||||
}
|
||||
|
||||
private void tryCloseInventory(boolean sendClosePacket) {
|
||||
var closedInventory = getOpenInventory();
|
||||
if (closedInventory == null) return;
|
||||
|
||||
didCloseInventory = !sendClosePacket;
|
||||
|
||||
if (closedInventory.removeViewer(this)) {
|
||||
if (closedInventory == getOpenInventory()) {
|
||||
this.openInventory = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the specified Inventory, close the previous inventory if existing.
|
||||
*
|
||||
|
@ -1736,21 +1760,12 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||
InventoryOpenEvent inventoryOpenEvent = new InventoryOpenEvent(inventory, this);
|
||||
|
||||
EventDispatcher.callCancellable(inventoryOpenEvent, () -> {
|
||||
Inventory openInventory = getOpenInventory();
|
||||
if (openInventory != null) {
|
||||
openInventory.removeViewer(this);
|
||||
}
|
||||
tryCloseInventory(false);
|
||||
|
||||
Inventory newInventory = inventoryOpenEvent.getInventory();
|
||||
if (newInventory == null) {
|
||||
// just close the inventory
|
||||
return;
|
||||
if (newInventory.addViewer(this)) {
|
||||
this.openInventory = newInventory;
|
||||
}
|
||||
|
||||
sendPacket(new OpenWindowPacket(newInventory.getWindowId(),
|
||||
newInventory.getInventoryType().getWindowType(), newInventory.getTitle()));
|
||||
newInventory.addViewer(this);
|
||||
this.openInventory = newInventory;
|
||||
});
|
||||
return !inventoryOpenEvent.isCancelled();
|
||||
}
|
||||
|
@ -1760,42 +1775,13 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||
* It closes the player inventory (when opened) if {@link #getOpenInventory()} returns null.
|
||||
*/
|
||||
public void closeInventory() {
|
||||
closeInventory(false);
|
||||
closeInventory(true);
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public void closeInventory(boolean fromClient) {
|
||||
Inventory openInventory = getOpenInventory();
|
||||
|
||||
// Drop cursor item when closing inventory
|
||||
ItemStack cursorItem;
|
||||
if (openInventory == null) {
|
||||
cursorItem = getInventory().getCursorItem();
|
||||
getInventory().setCursorItem(ItemStack.AIR);
|
||||
} else {
|
||||
cursorItem = openInventory.getCursorItem(this);
|
||||
openInventory.setCursorItem(this, ItemStack.AIR);
|
||||
}
|
||||
if (!cursorItem.isAir()) {
|
||||
// Add item to inventory if he hasn't been able to drop it
|
||||
if (!dropItem(cursorItem)) {
|
||||
getInventory().addItemStack(cursorItem);
|
||||
}
|
||||
}
|
||||
|
||||
if (openInventory == getOpenInventory()) {
|
||||
CloseWindowPacket closeWindowPacket;
|
||||
if (openInventory == null) {
|
||||
closeWindowPacket = new CloseWindowPacket((byte) 0);
|
||||
} else {
|
||||
closeWindowPacket = new CloseWindowPacket(openInventory.getWindowId());
|
||||
openInventory.removeViewer(this); // Clear cache
|
||||
this.openInventory = null;
|
||||
}
|
||||
if (!fromClient) sendPacket(closeWindowPacket);
|
||||
inventory.update();
|
||||
this.didCloseInventory = true;
|
||||
}
|
||||
public void closeInventory(boolean sendClosePacket) {
|
||||
tryCloseInventory(sendClosePacket);
|
||||
inventory.update();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1840,7 +1826,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||
|
||||
/**
|
||||
* Used to synchronize player position with viewers on spawn or after {@link Entity#teleport(Pos, long[], int)}
|
||||
* in cases where a {@link PlayerPositionAndLookPacket} is required
|
||||
* in properties where a {@link PlayerPositionAndLookPacket} is required
|
||||
*
|
||||
* @param position the position used by {@link PlayerPositionAndLookPacket}
|
||||
* this may not be the same as the {@link Entity#position}
|
||||
|
@ -2215,7 +2201,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||
return null;
|
||||
|
||||
final ItemStack updatedItem = getItemInHand(hand);
|
||||
final boolean isFood = updatedItem.material().isFood();
|
||||
final boolean isFood = updatedItem.has(ItemComponent.FOOD);
|
||||
|
||||
if (isFood && !allowFood)
|
||||
return null;
|
||||
|
@ -2304,62 +2290,62 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||
|
||||
@Override
|
||||
public @NotNull ItemStack getItemInMainHand() {
|
||||
return inventory.getItemInMainHand();
|
||||
return inventory.getEquipment(EquipmentSlot.MAIN_HAND, getHeldSlot());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItemInMainHand(@NotNull ItemStack itemStack) {
|
||||
inventory.setItemInMainHand(itemStack);
|
||||
inventory.setEquipment(EquipmentSlot.MAIN_HAND, getHeldSlot(), itemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ItemStack getItemInOffHand() {
|
||||
return inventory.getItemInOffHand();
|
||||
return inventory.getEquipment(EquipmentSlot.OFF_HAND, getHeldSlot());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItemInOffHand(@NotNull ItemStack itemStack) {
|
||||
inventory.setItemInOffHand(itemStack);
|
||||
inventory.setEquipment(EquipmentSlot.OFF_HAND, getHeldSlot(), itemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ItemStack getHelmet() {
|
||||
return inventory.getHelmet();
|
||||
return inventory.getEquipment(EquipmentSlot.HELMET, getHeldSlot());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHelmet(@NotNull ItemStack itemStack) {
|
||||
inventory.setHelmet(itemStack);
|
||||
inventory.setEquipment(EquipmentSlot.HELMET, getHeldSlot(), itemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ItemStack getChestplate() {
|
||||
return inventory.getChestplate();
|
||||
return inventory.getEquipment(EquipmentSlot.CHESTPLATE, getHeldSlot());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChestplate(@NotNull ItemStack itemStack) {
|
||||
inventory.setChestplate(itemStack);
|
||||
inventory.setEquipment(EquipmentSlot.CHESTPLATE, getHeldSlot(), itemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ItemStack getLeggings() {
|
||||
return inventory.getLeggings();
|
||||
return inventory.getEquipment(EquipmentSlot.LEGGINGS, getHeldSlot());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLeggings(@NotNull ItemStack itemStack) {
|
||||
inventory.setLeggings(itemStack);
|
||||
inventory.setEquipment(EquipmentSlot.LEGGINGS, getHeldSlot(), itemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ItemStack getBoots() {
|
||||
return inventory.getBoots();
|
||||
return inventory.getEquipment(EquipmentSlot.BOOTS, getHeldSlot());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBoots(@NotNull ItemStack itemStack) {
|
||||
inventory.setBoots(itemStack);
|
||||
inventory.setEquipment(EquipmentSlot.BOOTS, getHeldSlot(), itemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
package net.minestom.server.entity.attribute;
|
||||
|
||||
import net.minestom.server.registry.Registry;
|
||||
import net.minestom.server.registry.StaticProtocolObject;
|
||||
import net.minestom.server.utils.NamespaceID;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public sealed interface Attribute extends StaticProtocolObject, Attributes permits AttributeImpl {
|
||||
|
||||
@Contract(pure = true)
|
||||
@NotNull Registry.AttributeEntry registry();
|
||||
|
||||
@Override
|
||||
default @NotNull NamespaceID namespace() {
|
||||
return registry().namespace();
|
||||
}
|
||||
|
||||
@Override
|
||||
default int id() {
|
||||
return registry().id();
|
||||
}
|
||||
|
||||
default double defaultValue() {
|
||||
return registry().defaultValue();
|
||||
}
|
||||
|
||||
default double minValue() {
|
||||
return registry().minValue();
|
||||
}
|
||||
|
||||
default double maxValue() {
|
||||
return registry().maxValue();
|
||||
}
|
||||
|
||||
default boolean isSynced() {
|
||||
return registry().clientSync();
|
||||
}
|
||||
|
||||
static @NotNull Collection<@NotNull Attribute> values() {
|
||||
return AttributeImpl.values();
|
||||
}
|
||||
|
||||
static @Nullable Attribute fromNamespaceId(@NotNull String namespaceID) {
|
||||
return AttributeImpl.getSafe(namespaceID);
|
||||
}
|
||||
|
||||
static @Nullable Attribute fromNamespaceId(@NotNull NamespaceID namespaceID) {
|
||||
return fromNamespaceId(namespaceID.asString());
|
||||
}
|
||||
|
||||
static @Nullable Attribute fromId(int id) {
|
||||
return AttributeImpl.getId(id);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package net.minestom.server.entity.attribute;
|
||||
|
||||
import net.minestom.server.registry.Registry;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
record AttributeImpl(@NotNull Registry.AttributeEntry registry) implements Attribute {
|
||||
private static final Registry.Container<Attribute> CONTAINER = Registry.createStaticContainer(Registry.Resource.ATTRIBUTES,
|
||||
(namespace, properties) -> new AttributeImpl(Registry.attribute(namespace, properties)));
|
||||
|
||||
static Attribute get(@NotNull String namespace) {
|
||||
return CONTAINER.get(namespace);
|
||||
}
|
||||
|
||||
static Attribute getSafe(@NotNull String namespace) {
|
||||
return CONTAINER.getSafe(namespace);
|
||||
}
|
||||
|
||||
static Attribute getId(int id) {
|
||||
return CONTAINER.getId(id);
|
||||
}
|
||||
|
||||
static Collection<Attribute> values() {
|
||||
return CONTAINER.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name();
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package net.minestom.server.attribute;
|
||||
package net.minestom.server.entity.attribute;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
@ -16,8 +16,8 @@ public final class AttributeInstance {
|
|||
private final Attribute attribute;
|
||||
private final Map<UUID, AttributeModifier> modifiers = new HashMap<>();
|
||||
private final Consumer<AttributeInstance> propertyChangeListener;
|
||||
private float baseValue;
|
||||
private float cachedValue = 0.0f;
|
||||
private double baseValue;
|
||||
private double cachedValue = 0.0f;
|
||||
|
||||
public AttributeInstance(@NotNull Attribute attribute, @Nullable Consumer<AttributeInstance> listener) {
|
||||
this.attribute = attribute;
|
||||
|
@ -39,9 +39,9 @@ public final class AttributeInstance {
|
|||
* The base value of this instance without modifiers
|
||||
*
|
||||
* @return the instance base value
|
||||
* @see #setBaseValue(float)
|
||||
* @see #setBaseValue(double)
|
||||
*/
|
||||
public float getBaseValue() {
|
||||
public double getBaseValue() {
|
||||
return baseValue;
|
||||
}
|
||||
|
||||
|
@ -51,7 +51,7 @@ public final class AttributeInstance {
|
|||
* @param baseValue the new base value
|
||||
* @see #getBaseValue()
|
||||
*/
|
||||
public void setBaseValue(float baseValue) {
|
||||
public void setBaseValue(double baseValue) {
|
||||
if (this.baseValue != baseValue) {
|
||||
this.baseValue = baseValue;
|
||||
refreshCachedValue();
|
||||
|
@ -104,7 +104,7 @@ public final class AttributeInstance {
|
|||
*
|
||||
* @return the attribute value
|
||||
*/
|
||||
public float getValue() {
|
||||
public double getValue() {
|
||||
return cachedValue;
|
||||
}
|
||||
|
||||
|
@ -113,13 +113,13 @@ public final class AttributeInstance {
|
|||
*/
|
||||
private void refreshCachedValue() {
|
||||
final Collection<AttributeModifier> modifiers = getModifiers();
|
||||
float base = getBaseValue();
|
||||
double base = getBaseValue();
|
||||
|
||||
for (var modifier : modifiers.stream().filter(mod -> mod.getOperation() == AttributeOperation.ADDITION).toArray(AttributeModifier[]::new)) {
|
||||
for (var modifier : modifiers.stream().filter(mod -> mod.getOperation() == AttributeOperation.ADD_VALUE).toArray(AttributeModifier[]::new)) {
|
||||
base += modifier.getAmount();
|
||||
}
|
||||
|
||||
float result = base;
|
||||
double result = base;
|
||||
|
||||
for (var modifier : modifiers.stream().filter(mod -> mod.getOperation() == AttributeOperation.MULTIPLY_BASE).toArray(AttributeModifier[]::new)) {
|
||||
result += (base * modifier.getAmount());
|
|
@ -1,5 +1,6 @@
|
|||
package net.minestom.server.attribute;
|
||||
package net.minestom.server.entity.attribute;
|
||||
|
||||
import net.minestom.server.network.NetworkBuffer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
|
@ -7,12 +8,12 @@ import java.util.UUID;
|
|||
/**
|
||||
* Represent an attribute modifier.
|
||||
*/
|
||||
public class AttributeModifier {
|
||||
|
||||
private final double amount;
|
||||
private final String name;
|
||||
private final AttributeOperation operation;
|
||||
private final UUID id;
|
||||
public record AttributeModifier(
|
||||
@NotNull UUID id,
|
||||
@NotNull String name,
|
||||
double amount,
|
||||
@NotNull AttributeOperation operation
|
||||
) implements NetworkBuffer.Writer {
|
||||
|
||||
/**
|
||||
* Creates a new modifier with a random id.
|
||||
|
@ -25,19 +26,17 @@ public class AttributeModifier {
|
|||
this(UUID.randomUUID(), name, amount, operation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new modifier.
|
||||
*
|
||||
* @param id the id of this modifier
|
||||
* @param name the name of this modifier
|
||||
* @param amount the value of this modifier
|
||||
* @param operation the operation to apply this modifier with
|
||||
*/
|
||||
public AttributeModifier(@NotNull UUID id, @NotNull String name, double amount, @NotNull AttributeOperation operation) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.amount = amount;
|
||||
this.operation = operation;
|
||||
public AttributeModifier(@NotNull NetworkBuffer reader) {
|
||||
this(reader.read(NetworkBuffer.UUID), reader.read(NetworkBuffer.STRING),
|
||||
reader.read(NetworkBuffer.DOUBLE), reader.readEnum(AttributeOperation.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(@NotNull NetworkBuffer writer) {
|
||||
writer.write(NetworkBuffer.UUID, id);
|
||||
writer.write(NetworkBuffer.STRING, name);
|
||||
writer.write(NetworkBuffer.DOUBLE, amount);
|
||||
writer.writeEnum(AttributeOperation.class, operation);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -45,8 +44,8 @@ public class AttributeModifier {
|
|||
*
|
||||
* @return the id of this modifier
|
||||
*/
|
||||
@NotNull
|
||||
public UUID getId() {
|
||||
@Deprecated
|
||||
public @NotNull UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
|
@ -55,8 +54,8 @@ public class AttributeModifier {
|
|||
*
|
||||
* @return the name of this modifier
|
||||
*/
|
||||
@NotNull
|
||||
public String getName() {
|
||||
@Deprecated
|
||||
public @NotNull String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
|
@ -65,6 +64,7 @@ public class AttributeModifier {
|
|||
*
|
||||
* @return the value of this modifier
|
||||
*/
|
||||
@Deprecated
|
||||
public double getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
@ -74,8 +74,8 @@ public class AttributeModifier {
|
|||
*
|
||||
* @return the operation of this modifier
|
||||
*/
|
||||
@NotNull
|
||||
public AttributeOperation getOperation() {
|
||||
@Deprecated
|
||||
public @NotNull AttributeOperation getOperation() {
|
||||
return operation;
|
||||
}
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
package net.minestom.server.attribute;
|
||||
package net.minestom.server.entity.attribute;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public enum AttributeOperation {
|
||||
ADDITION(0),
|
||||
ADD_VALUE(0),
|
||||
MULTIPLY_BASE(1),
|
||||
MULTIPLY_TOTAL(2);
|
||||
|
||||
private static final AttributeOperation[] VALUES = new AttributeOperation[]{ADDITION, MULTIPLY_BASE, MULTIPLY_TOTAL};
|
||||
private static final AttributeOperation[] VALUES = new AttributeOperation[]{ADD_VALUE, MULTIPLY_BASE, MULTIPLY_TOTAL};
|
||||
private final int id;
|
||||
|
||||
AttributeOperation(int id) {
|
|
@ -1,12 +1,12 @@
|
|||
package net.minestom.server.entity.damage;
|
||||
|
||||
import net.minestom.server.registry.StaticProtocolObject;
|
||||
import net.minestom.server.network.packet.server.configuration.RegistryDataPacket;
|
||||
import net.minestom.server.registry.Registry;
|
||||
import net.minestom.server.registry.StaticProtocolObject;
|
||||
import net.minestom.server.utils.NamespaceID;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
|
@ -36,7 +36,7 @@ public sealed interface DamageType extends StaticProtocolObject, DamageTypes per
|
|||
return registry().scaling();
|
||||
}
|
||||
|
||||
NBTCompound asNBT();
|
||||
@NotNull RegistryDataPacket.Entry toRegistryEntry();
|
||||
|
||||
static @NotNull Collection<@NotNull DamageType> values() {
|
||||
return DamageTypeImpl.values();
|
||||
|
@ -54,7 +54,7 @@ public sealed interface DamageType extends StaticProtocolObject, DamageTypes per
|
|||
return DamageTypeImpl.getId(id);
|
||||
}
|
||||
|
||||
static NBTCompound getNBT() {
|
||||
return DamageTypeImpl.getNBT();
|
||||
static @NotNull RegistryDataPacket registryDataPacket() {
|
||||
return DamageTypeImpl.registryDataPacket();
|
||||
}
|
||||
}
|
|
@ -1,14 +1,11 @@
|
|||
package net.minestom.server.entity.damage;
|
||||
|
||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||
import net.minestom.server.network.packet.server.configuration.RegistryDataPacket;
|
||||
import net.minestom.server.registry.Registry;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBT;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTType;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
record DamageTypeImpl(Registry.DamageTypeEntry registry, int id) implements DamageType {
|
||||
|
@ -32,13 +29,14 @@ record DamageTypeImpl(Registry.DamageTypeEntry registry, int id) implements Dama
|
|||
return CONTAINER.getId(id);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NBTCompound asNBT() {
|
||||
var elem = new HashMap<String, NBT>();
|
||||
elem.put("exhaustion", NBT.Float(registry.exhaustion()));
|
||||
elem.put("message_id", NBT.String(registry.messageId()));
|
||||
elem.put("scaling", NBT.String(registry.scaling()));
|
||||
return NBT.Compound(elem);
|
||||
public @NotNull RegistryDataPacket.Entry toRegistryEntry() {
|
||||
return new RegistryDataPacket.Entry(name(), CompoundBinaryTag.builder()
|
||||
.putFloat("exhaustion", registry.exhaustion())
|
||||
.putString("message_id", registry.messageId())
|
||||
.putString("scaling", registry.scaling())
|
||||
.build());
|
||||
}
|
||||
|
||||
static Collection<DamageType> values() {
|
||||
|
@ -55,23 +53,15 @@ record DamageTypeImpl(Registry.DamageTypeEntry registry, int id) implements Dama
|
|||
return id;
|
||||
}
|
||||
|
||||
private static NBTCompound lazyNbt = null;
|
||||
private static RegistryDataPacket lazyRegistryDataPacket = null;
|
||||
|
||||
static NBTCompound getNBT() {
|
||||
if (lazyNbt == null) {
|
||||
var damageTypes = values().stream()
|
||||
.map((damageType) -> NBT.Compound(Map.of(
|
||||
"id", NBT.Int(damageType.id()),
|
||||
"name", NBT.String(damageType.name()),
|
||||
"element", damageType.asNBT()
|
||||
)))
|
||||
.toList();
|
||||
|
||||
lazyNbt = NBT.Compound(Map.of(
|
||||
"type", NBT.String("minecraft:damage_type"),
|
||||
"value", NBT.List(NBTType.TAG_Compound, damageTypes)
|
||||
));
|
||||
}
|
||||
return lazyNbt;
|
||||
static @NotNull RegistryDataPacket registryDataPacket() {
|
||||
if (lazyRegistryDataPacket != null) return lazyRegistryDataPacket;
|
||||
return lazyRegistryDataPacket = new RegistryDataPacket(
|
||||
"minecraft:damage_type",
|
||||
values().stream()
|
||||
.map(DamageType::toRegistryEntry)
|
||||
.toList()
|
||||
);
|
||||
}
|
||||
}
|
|
@ -4,9 +4,12 @@ import net.minestom.server.coordinate.Point;
|
|||
import net.minestom.server.entity.Entity;
|
||||
import net.minestom.server.entity.Metadata;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.particle.Particle;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class LivingEntityMeta extends EntityMeta {
|
||||
public static final byte OFFSET = EntityMeta.MAX_OFFSET;
|
||||
public static final byte MAX_OFFSET = OFFSET + 7;
|
||||
|
@ -52,12 +55,12 @@ public class LivingEntityMeta extends EntityMeta {
|
|||
super.metadata.setIndex(OFFSET + 1, Metadata.Float(value));
|
||||
}
|
||||
|
||||
public int getPotionEffectColor() {
|
||||
return super.metadata.getIndex(OFFSET + 2, 0);
|
||||
public @NotNull List<Particle> getEffectParticles() {
|
||||
return super.metadata.getIndex(OFFSET + 2, List.of());
|
||||
}
|
||||
|
||||
public void setPotionEffectColor(int value) {
|
||||
super.metadata.setIndex(OFFSET + 2, Metadata.VarInt(value));
|
||||
public void setEffectParticles(@NotNull List<Particle> value) {
|
||||
super.metadata.setIndex(OFFSET + 2, Metadata.ParticleList(value));
|
||||
}
|
||||
|
||||
public boolean isPotionEffectAmbient() {
|
||||
|
@ -76,28 +79,6 @@ public class LivingEntityMeta extends EntityMeta {
|
|||
super.metadata.setIndex(OFFSET + 4, Metadata.VarInt(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* This returns the bee stinger count, not the absorption heart count
|
||||
* Use {@link #getBeeStingerCount()} instead
|
||||
* @return The number of bee stingers in this entity
|
||||
*/
|
||||
@Deprecated
|
||||
public int getHealthAddedByAbsorption() {
|
||||
return super.metadata.getIndex(OFFSET + 5, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* This sets the bee stinger count, not the absorption heart count
|
||||
* Use {@link #setBeeStingerCount(int)} instead
|
||||
* @param value The number of bee stingers for this entity to have
|
||||
*/
|
||||
@Deprecated
|
||||
public void setHealthAddedByAbsorption(int value) {
|
||||
super.metadata.setIndex(OFFSET + 5, Metadata.VarInt(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the amount of bee stingers in this entity
|
||||
* @return The amount of bee stingers
|
||||
|
@ -120,7 +101,7 @@ public class LivingEntityMeta extends EntityMeta {
|
|||
}
|
||||
|
||||
public void setBedInWhichSleepingPosition(@Nullable Point value) {
|
||||
super.metadata.setIndex(OFFSET + 6, Metadata.OptPosition(value));
|
||||
super.metadata.setIndex(OFFSET + 6, Metadata.OptBlockPosition(value));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
package net.minestom.server.entity.metadata;
|
||||
|
||||
import net.kyori.adventure.nbt.BinaryTag;
|
||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||
import net.minestom.server.entity.Entity;
|
||||
import net.minestom.server.entity.Metadata;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBT;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class PlayerMeta extends LivingEntityMeta {
|
||||
public static final byte OFFSET = LivingEntityMeta.MAX_OFFSET;
|
||||
|
@ -109,23 +108,23 @@ public class PlayerMeta extends LivingEntityMeta {
|
|||
}
|
||||
|
||||
@Nullable
|
||||
public NBT getLeftShoulderEntityData() {
|
||||
public BinaryTag getLeftShoulderEntityData() {
|
||||
return super.metadata.getIndex(OFFSET + 4, null);
|
||||
}
|
||||
|
||||
public void setLeftShoulderEntityData(@Nullable NBT value) {
|
||||
if (value == null) value = NBT.Compound(Map.of());
|
||||
public void setLeftShoulderEntityData(@Nullable BinaryTag value) {
|
||||
if (value == null) value = CompoundBinaryTag.empty();
|
||||
|
||||
super.metadata.setIndex(OFFSET + 4, Metadata.NBT(value));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public NBT getRightShoulderEntityData() {
|
||||
public BinaryTag getRightShoulderEntityData() {
|
||||
return super.metadata.getIndex(OFFSET + 5, null);
|
||||
}
|
||||
|
||||
public void setRightShoulderEntityData(@Nullable NBT value) {
|
||||
if (value == null) value = NBT.Compound(Map.of());
|
||||
public void setRightShoulderEntityData(@Nullable BinaryTag value) {
|
||||
if (value == null) value = CompoundBinaryTag.empty();
|
||||
|
||||
super.metadata.setIndex(OFFSET + 5, Metadata.NBT(value));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
package net.minestom.server.entity.metadata.animal;
|
||||
|
||||
import net.minestom.server.entity.Entity;
|
||||
import net.minestom.server.entity.Metadata;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ArmadilloMeta extends AnimalMeta {
|
||||
public static final byte OFFSET = AnimalMeta.MAX_OFFSET;
|
||||
public static final byte MAX_OFFSET = OFFSET + 1;
|
||||
|
||||
public ArmadilloMeta(@NotNull Entity entity, @NotNull Metadata metadata) {
|
||||
super(entity, metadata);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public State getState() {
|
||||
return super.metadata.getIndex(OFFSET, State.IDLE);
|
||||
}
|
||||
|
||||
public void setState(@NotNull State value) {
|
||||
super.metadata.setIndex(OFFSET, Metadata.ArmadilloState(value));
|
||||
}
|
||||
|
||||
public enum State {
|
||||
IDLE,
|
||||
ROLLING,
|
||||
SCARED,
|
||||
UNROLLING;
|
||||
|
||||
private static final State[] VALUES = values();
|
||||
}
|
||||
}
|
|
@ -19,7 +19,7 @@ public class TurtleMeta extends AnimalMeta {
|
|||
}
|
||||
|
||||
public void setBlockPosition(@NotNull Point value) {
|
||||
super.metadata.setIndex(OFFSET, Metadata.Position(value));
|
||||
super.metadata.setIndex(OFFSET, Metadata.BlockPosition(value));
|
||||
}
|
||||
|
||||
public boolean isHasEgg() {
|
||||
|
@ -43,7 +43,7 @@ public class TurtleMeta extends AnimalMeta {
|
|||
}
|
||||
|
||||
public void setTravelPosition(@NotNull Point value) {
|
||||
super.metadata.setIndex(OFFSET + 3, Metadata.Position(value));
|
||||
super.metadata.setIndex(OFFSET + 3, Metadata.BlockPosition(value));
|
||||
}
|
||||
|
||||
public boolean isGoingHome() {
|
||||
|
|
|
@ -12,6 +12,8 @@ public class WolfMeta extends TameableAnimalMeta {
|
|||
super(entity, metadata);
|
||||
}
|
||||
|
||||
//todo variant
|
||||
|
||||
public boolean isBegging() {
|
||||
return super.metadata.getIndex(OFFSET, false);
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ public class ItemDisplayMeta extends AbstractDisplayMeta {
|
|||
}
|
||||
|
||||
public void setItemStack(@NotNull ItemStack value) {
|
||||
super.metadata.setIndex(OFFSET, Metadata.Slot(value));
|
||||
super.metadata.setIndex(OFFSET, Metadata.ItemStack(value));
|
||||
}
|
||||
|
||||
public @NotNull DisplayContext getDisplayContext() {
|
||||
|
|
|
@ -24,7 +24,7 @@ class ItemContainingMeta extends EntityMeta {
|
|||
}
|
||||
|
||||
public void setItem(@NotNull ItemStack item) {
|
||||
super.metadata.setIndex(OFFSET, Metadata.Slot(item));
|
||||
super.metadata.setIndex(OFFSET, Metadata.ItemStack(item));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package net.minestom.server.entity.metadata.monster.skeleton;
|
||||
|
||||
import net.minestom.server.entity.Entity;
|
||||
import net.minestom.server.entity.Metadata;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class BoggedMeta extends AbstractSkeletonMeta {
|
||||
public static final byte OFFSET = AbstractSkeletonMeta.MAX_OFFSET;
|
||||
public static final byte MAX_OFFSET = OFFSET + 0;
|
||||
|
||||
public BoggedMeta(@NotNull Entity entity, @NotNull Metadata metadata) {
|
||||
super(entity, metadata);
|
||||
}
|
||||
}
|
|
@ -20,7 +20,7 @@ public class EndCrystalMeta extends EntityMeta {
|
|||
}
|
||||
|
||||
public void setBeamTarget(@Nullable Point value) {
|
||||
super.metadata.setIndex(OFFSET, Metadata.OptPosition(value));
|
||||
super.metadata.setIndex(OFFSET, Metadata.OptBlockPosition(value));
|
||||
}
|
||||
|
||||
public boolean isShowingBottom() {
|
||||
|
|
|
@ -24,7 +24,7 @@ public class FallingBlockMeta extends EntityMeta implements ObjectDataProvider {
|
|||
}
|
||||
|
||||
public void setSpawnPosition(Point value) {
|
||||
super.metadata.setIndex(OFFSET, Metadata.Position(value));
|
||||
super.metadata.setIndex(OFFSET, Metadata.BlockPosition(value));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
|
|
@ -25,7 +25,7 @@ public class ItemFrameMeta extends EntityMeta implements ObjectDataProvider {
|
|||
}
|
||||
|
||||
public void setItem(@NotNull ItemStack value) {
|
||||
super.metadata.setIndex(OFFSET, Metadata.Slot(value));
|
||||
super.metadata.setIndex(OFFSET, Metadata.ItemStack(value));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package net.minestom.server.entity.metadata.other;
|
||||
|
||||
import net.minestom.server.entity.Entity;
|
||||
import net.minestom.server.entity.Metadata;
|
||||
import net.minestom.server.entity.metadata.EntityMeta;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class OminousItemSpawnerMeta extends EntityMeta {
|
||||
public static final byte OFFSET = EntityMeta.MAX_OFFSET;
|
||||
public static final byte MAX_OFFSET = OFFSET + 1;
|
||||
|
||||
public OminousItemSpawnerMeta(@NotNull Entity entity, @NotNull Metadata metadata) {
|
||||
super(entity, metadata);
|
||||
}
|
||||
|
||||
public @NotNull ItemStack getItem() {
|
||||
return super.metadata.getIndex(OFFSET, ItemStack.AIR);
|
||||
}
|
||||
|
||||
public void setItem(@NotNull ItemStack value) {
|
||||
super.metadata.setIndex(OFFSET, Metadata.ItemStack(value));
|
||||
}
|
||||
|
||||
}
|
|
@ -37,7 +37,7 @@ public class ArrowMeta extends AbstractArrowMeta implements ObjectDataProvider,
|
|||
|
||||
@Override
|
||||
public int getObjectData() {
|
||||
return this.shooter == null ? 0 : this.shooter.getEntityId() + 1;
|
||||
return this.shooter == null ? 0 : this.shooter.getEntityId();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
package net.minestom.server.entity.metadata.projectile;
|
||||
|
||||
import net.minestom.server.entity.Entity;
|
||||
import net.minestom.server.entity.Metadata;
|
||||
import net.minestom.server.entity.metadata.EntityMeta;
|
||||
import net.minestom.server.entity.metadata.ObjectDataProvider;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class BreezeWindChargeMeta extends EntityMeta implements ObjectDataProvider, ProjectileMeta {
|
||||
public static final byte OFFSET = EntityMeta.MAX_OFFSET;
|
||||
public static final byte MAX_OFFSET = OFFSET + 0;
|
||||
|
||||
private Entity shooter;
|
||||
|
||||
public BreezeWindChargeMeta(@NotNull Entity entity, @NotNull Metadata metadata) {
|
||||
super(entity, metadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Entity getShooter() {
|
||||
return shooter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShooter(@Nullable Entity shooter) {
|
||||
this.shooter = shooter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getObjectData() {
|
||||
return this.shooter == null ? 0 : this.shooter.getEntityId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresVelocityPacketAtSpawn() {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -3,7 +3,6 @@ package net.minestom.server.entity.metadata.projectile;
|
|||
import net.minestom.server.entity.Entity;
|
||||
import net.minestom.server.entity.Metadata;
|
||||
import net.minestom.server.entity.metadata.EntityMeta;
|
||||
import net.minestom.server.entity.metadata.projectile.ProjectileMeta;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
@ -24,7 +23,7 @@ public class FireworkRocketMeta extends EntityMeta implements ProjectileMeta {
|
|||
}
|
||||
|
||||
public void setFireworkInfo(@NotNull ItemStack value) {
|
||||
super.metadata.setIndex(OFFSET, Metadata.Slot(value));
|
||||
super.metadata.setIndex(OFFSET, Metadata.ItemStack(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -29,7 +29,7 @@ public class SpectralArrowMeta extends AbstractArrowMeta implements ObjectDataPr
|
|||
|
||||
@Override
|
||||
public int getObjectData() {
|
||||
return this.shooter == null ? 0 : this.shooter.getEntityId() + 1;
|
||||
return this.shooter == null ? 0 : this.shooter.getEntityId();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -20,7 +20,7 @@ public class DolphinMeta extends WaterAnimalMeta {
|
|||
}
|
||||
|
||||
public void setTreasurePosition(@NotNull Point value) {
|
||||
super.metadata.setIndex(OFFSET, Metadata.Position(value));
|
||||
super.metadata.setIndex(OFFSET, Metadata.BlockPosition(value));
|
||||
}
|
||||
|
||||
public boolean isHasFish() {
|
||||
|
|
|
@ -4,11 +4,11 @@ import com.extollit.gaming.ai.path.model.Gravitation;
|
|||
import com.extollit.gaming.ai.path.model.IPathingEntity;
|
||||
import com.extollit.gaming.ai.path.model.Passibility;
|
||||
import com.extollit.linalg.immutable.Vec3d;
|
||||
import net.minestom.server.attribute.Attribute;
|
||||
import net.minestom.server.coordinate.Point;
|
||||
import net.minestom.server.coordinate.Vec;
|
||||
import net.minestom.server.entity.Entity;
|
||||
import net.minestom.server.entity.LivingEntity;
|
||||
import net.minestom.server.entity.attribute.Attribute;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
@ -34,7 +34,7 @@ public final class PFPathingEntity implements IPathingEntity {
|
|||
this.navigator = navigator;
|
||||
this.entity = navigator.getEntity();
|
||||
|
||||
this.searchRange = getAttributeValue(Attribute.FOLLOW_RANGE);
|
||||
this.searchRange = (float) getAttributeValue(Attribute.GENERIC_FOLLOW_RANGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -138,7 +138,7 @@ public final class PFPathingEntity implements IPathingEntity {
|
|||
return new Capabilities() {
|
||||
@Override
|
||||
public float speed() {
|
||||
return getAttributeValue(Attribute.MOVEMENT_SPEED);
|
||||
return (float) getAttributeValue(Attribute.GENERIC_MOVEMENT_SPEED);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -191,7 +191,7 @@ public final class PFPathingEntity implements IPathingEntity {
|
|||
@Override
|
||||
public void moveTo(Vec3d position, Passibility passibility, Gravitation gravitation) {
|
||||
final Point targetPosition = new Vec(position.x, position.y, position.z);
|
||||
this.navigator.moveTowards(targetPosition, getAttributeValue(Attribute.MOVEMENT_SPEED));
|
||||
this.navigator.moveTowards(targetPosition, getAttributeValue(Attribute.GENERIC_MOVEMENT_SPEED));
|
||||
final double entityY = entity.getPosition().y() + 0.00001D; // After any negative y movement, entities will always be extremely
|
||||
// slightly below floor level. This +0.00001D is here to offset this
|
||||
// error and stop the entity from permanently jumping.
|
||||
|
@ -217,7 +217,7 @@ public final class PFPathingEntity implements IPathingEntity {
|
|||
return (float) entity.getBoundingBox().height();
|
||||
}
|
||||
|
||||
private float getAttributeValue(@NotNull Attribute attribute) {
|
||||
private double getAttributeValue(@NotNull Attribute attribute) {
|
||||
if (entity instanceof LivingEntity) {
|
||||
return ((LivingEntity) entity).getAttributeValue(attribute);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
package net.minestom.server.event.inventory;
|
||||
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.event.trait.InventoryEvent;
|
||||
import net.minestom.server.event.trait.PlayerInstanceEvent;
|
||||
import net.minestom.server.inventory.Inventory;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Called when a player clicks an inventory button.
|
||||
* See <a href="https://wiki.vg/Protocol#Click_Container_Button">wiki.vg</a> for slot number details.
|
||||
*/
|
||||
public class InventoryButtonClickEvent implements InventoryEvent, PlayerInstanceEvent {
|
||||
|
||||
private final Inventory inventory;
|
||||
private final Player player;
|
||||
private final byte button;
|
||||
|
||||
public InventoryButtonClickEvent(@NotNull Inventory inventory, @NotNull Player player, byte button) {
|
||||
this.inventory = inventory;
|
||||
this.player = player;
|
||||
this.button = button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player who clicked the button in the inventory.
|
||||
*
|
||||
* @return the player who clicked
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Inventory getInventory() {
|
||||
return inventory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the inventory button number that the player clicked. This is different from inventory slots.
|
||||
* @return the button clicked by the player
|
||||
*/
|
||||
public byte getButton() {
|
||||
return button;
|
||||
}
|
||||
}
|
|
@ -1,89 +1,95 @@
|
|||
package net.minestom.server.event.inventory;
|
||||
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.event.trait.CancellableEvent;
|
||||
import net.minestom.server.event.trait.InventoryEvent;
|
||||
import net.minestom.server.event.trait.PlayerInstanceEvent;
|
||||
import net.minestom.server.inventory.Inventory;
|
||||
import net.minestom.server.inventory.click.ClickType;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.inventory.PlayerInventory;
|
||||
import net.minestom.server.inventory.click.Click;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Called after {@link InventoryPreClickEvent}, this event cannot be cancelled and items related to the click
|
||||
* are already moved.
|
||||
* Called after {@link InventoryPreClickEvent} and before {@link InventoryPostClickEvent}.
|
||||
*/
|
||||
public class InventoryClickEvent implements InventoryEvent, PlayerInstanceEvent {
|
||||
public class InventoryClickEvent implements InventoryEvent, PlayerInstanceEvent, CancellableEvent {
|
||||
|
||||
private final PlayerInventory playerInventory;
|
||||
private final Inventory inventory;
|
||||
private final Player player;
|
||||
private final int slot;
|
||||
private final ClickType clickType;
|
||||
private final ItemStack clickedItem;
|
||||
private final ItemStack cursorItem;
|
||||
private final Click.Info info;
|
||||
private List<Click.Change> changes;
|
||||
|
||||
public InventoryClickEvent(@Nullable Inventory inventory, @NotNull Player player,
|
||||
int slot, @NotNull ClickType clickType,
|
||||
@NotNull ItemStack clicked, @NotNull ItemStack cursor) {
|
||||
private boolean cancelled;
|
||||
|
||||
public InventoryClickEvent(@NotNull PlayerInventory playerInventory, @NotNull Inventory inventory,
|
||||
@NotNull Player player, @NotNull Click.Info info, @NotNull List<Click.Change> changes) {
|
||||
this.playerInventory = playerInventory;
|
||||
this.inventory = inventory;
|
||||
this.player = player;
|
||||
this.slot = slot;
|
||||
this.clickType = clickType;
|
||||
this.clickedItem = clicked;
|
||||
this.cursorItem = cursor;
|
||||
this.info = info;
|
||||
this.changes = changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player who clicked in the inventory.
|
||||
* Gets the player who is trying to click on the inventory.
|
||||
*
|
||||
* @return the player who clicked in the inventory
|
||||
* @return the player who clicked
|
||||
*/
|
||||
@NotNull
|
||||
public Player getPlayer() {
|
||||
public @NotNull Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the clicked slot number.
|
||||
* Gets the info about the click that occurred. This is enough to fully describe the click.
|
||||
*
|
||||
* @return the clicked slot number
|
||||
* @return the click info
|
||||
*/
|
||||
public int getSlot() {
|
||||
return slot;
|
||||
public @NotNull Click.Info getClickInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the click type.
|
||||
* Gets the changes that will occur as a result of this click.
|
||||
*
|
||||
* @return the click type
|
||||
* @return the changes
|
||||
*/
|
||||
@NotNull
|
||||
public ClickType getClickType() {
|
||||
return clickType;
|
||||
public @NotNull List<Click.Change> getChanges() {
|
||||
return changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the clicked item.
|
||||
* Updates the changes that will occur as a result of this click.
|
||||
*
|
||||
* @return the clicked item
|
||||
* @param changes the new results
|
||||
*/
|
||||
@NotNull
|
||||
public ItemStack getClickedItem() {
|
||||
return clickedItem;
|
||||
public void setChanges(@NotNull List<Click.Change> changes) {
|
||||
this.changes = changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the item in the player cursor.
|
||||
* Gets the player inventory that was involved with the click.
|
||||
*
|
||||
* @return the cursor item
|
||||
* @return the player inventory
|
||||
*/
|
||||
@NotNull
|
||||
public ItemStack getCursorItem() {
|
||||
return cursorItem;
|
||||
public @NotNull PlayerInventory getPlayerInventory() {
|
||||
return playerInventory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Inventory getInventory() {
|
||||
public @NotNull Inventory getInventory() {
|
||||
return inventory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
this.cancelled = cancel;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,9 +14,9 @@ public class InventoryCloseEvent implements InventoryEvent, PlayerInstanceEvent
|
|||
|
||||
private final Inventory inventory;
|
||||
private final Player player;
|
||||
private Inventory newInventory;
|
||||
private @Nullable Inventory newInventory;
|
||||
|
||||
public InventoryCloseEvent(@Nullable Inventory inventory, @NotNull Player player) {
|
||||
public InventoryCloseEvent(@NotNull Inventory inventory, @NotNull Player player) {
|
||||
this.inventory = inventory;
|
||||
this.player = player;
|
||||
}
|
||||
|
@ -36,8 +36,7 @@ public class InventoryCloseEvent implements InventoryEvent, PlayerInstanceEvent
|
|||
*
|
||||
* @return the new inventory to open, null if there isn't any
|
||||
*/
|
||||
@Nullable
|
||||
public Inventory getNewInventory() {
|
||||
public @Nullable Inventory getNewInventory() {
|
||||
return newInventory;
|
||||
}
|
||||
|
||||
|
@ -51,7 +50,7 @@ public class InventoryCloseEvent implements InventoryEvent, PlayerInstanceEvent
|
|||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Inventory getInventory() {
|
||||
public @NotNull Inventory getInventory() {
|
||||
return inventory;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,19 +2,14 @@ package net.minestom.server.event.inventory;
|
|||
|
||||
import net.minestom.server.event.trait.InventoryEvent;
|
||||
import net.minestom.server.event.trait.RecursiveEvent;
|
||||
import net.minestom.server.inventory.AbstractInventory;
|
||||
import net.minestom.server.inventory.Inventory;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Called when {@link AbstractInventory#safeItemInsert(int, ItemStack)} is being invoked.
|
||||
* Called when a slot was changed in an inventory.
|
||||
* This event cannot be cancelled and items related to the change are already moved.
|
||||
*
|
||||
* @see PlayerInventoryItemChangeEvent
|
||||
*/
|
||||
@SuppressWarnings("JavadocReference")
|
||||
public class InventoryItemChangeEvent implements InventoryEvent, RecursiveEvent {
|
||||
|
||||
private final Inventory inventory;
|
||||
|
@ -22,7 +17,7 @@ public class InventoryItemChangeEvent implements InventoryEvent, RecursiveEvent
|
|||
private final ItemStack previousItem;
|
||||
private final ItemStack newItem;
|
||||
|
||||
public InventoryItemChangeEvent(@Nullable Inventory inventory, int slot,
|
||||
public InventoryItemChangeEvent(@NotNull Inventory inventory, int slot,
|
||||
@NotNull ItemStack previousItem, @NotNull ItemStack newItem) {
|
||||
this.inventory = inventory;
|
||||
this.slot = slot;
|
||||
|
@ -58,7 +53,7 @@ public class InventoryItemChangeEvent implements InventoryEvent, RecursiveEvent
|
|||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Inventory getInventory() {
|
||||
public @NotNull Inventory getInventory() {
|
||||
return inventory;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import net.minestom.server.event.trait.InventoryEvent;
|
|||
import net.minestom.server.event.trait.PlayerInstanceEvent;
|
||||
import net.minestom.server.inventory.Inventory;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Called when a player open an {@link Inventory}.
|
||||
|
@ -15,12 +14,12 @@ import org.jetbrains.annotations.Nullable;
|
|||
*/
|
||||
public class InventoryOpenEvent implements InventoryEvent, PlayerInstanceEvent, CancellableEvent {
|
||||
|
||||
private Inventory inventory;
|
||||
private final Player player;
|
||||
private Inventory inventory;
|
||||
|
||||
private boolean cancelled;
|
||||
|
||||
public InventoryOpenEvent(@Nullable Inventory inventory, @NotNull Player player) {
|
||||
public InventoryOpenEvent(@NotNull Inventory inventory, @NotNull Player player) {
|
||||
this.inventory = inventory;
|
||||
this.player = player;
|
||||
}
|
||||
|
@ -36,13 +35,12 @@ public class InventoryOpenEvent implements InventoryEvent, PlayerInstanceEvent,
|
|||
}
|
||||
|
||||
/**
|
||||
* Gets the inventory to open, this could have been change by the {@link #setInventory(Inventory)}.
|
||||
* Gets the inventory to open.
|
||||
*
|
||||
* @return the inventory to open, null to just close the current inventory if any
|
||||
* @return the inventory to open
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public Inventory getInventory() {
|
||||
public @NotNull Inventory getInventory() {
|
||||
return inventory;
|
||||
}
|
||||
|
||||
|
@ -53,7 +51,7 @@ public class InventoryOpenEvent implements InventoryEvent, PlayerInstanceEvent,
|
|||
*
|
||||
* @param inventory the inventory to open
|
||||
*/
|
||||
public void setInventory(@Nullable Inventory inventory) {
|
||||
public void setInventory(@NotNull Inventory inventory) {
|
||||
this.inventory = inventory;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
package net.minestom.server.event.inventory;
|
||||
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.event.trait.InventoryEvent;
|
||||
import net.minestom.server.event.trait.PlayerInstanceEvent;
|
||||
import net.minestom.server.inventory.Inventory;
|
||||
import net.minestom.server.inventory.click.Click;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Called after {@link InventoryClickEvent}, this event cannot be cancelled and items related to the click
|
||||
* are already moved.
|
||||
*/
|
||||
public class InventoryPostClickEvent implements InventoryEvent, PlayerInstanceEvent {
|
||||
|
||||
private final Player player;
|
||||
private final Inventory inventory;
|
||||
private final Click.Info info;
|
||||
private final List<Click.Change> changes;
|
||||
|
||||
public InventoryPostClickEvent(@NotNull Player player, @NotNull Inventory inventory, @NotNull Click.Info info, @NotNull List<Click.Change> changes) {
|
||||
this.player = player;
|
||||
this.inventory = inventory;
|
||||
this.info = info;
|
||||
this.changes = changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player who clicked in the inventory.
|
||||
*
|
||||
* @return the player who clicked in the inventory
|
||||
*/
|
||||
@NotNull
|
||||
public Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the info about the click that was already processed.
|
||||
*
|
||||
* @return the click info
|
||||
*/
|
||||
public @NotNull Click.Info getClickInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the changes that occurred as a result of this click.
|
||||
*
|
||||
* @return the changes
|
||||
*/
|
||||
public @NotNull List<Click.Change> getChanges() {
|
||||
return changes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Inventory getInventory() {
|
||||
return inventory;
|
||||
}
|
||||
}
|
|
@ -5,35 +5,28 @@ import net.minestom.server.event.trait.CancellableEvent;
|
|||
import net.minestom.server.event.trait.InventoryEvent;
|
||||
import net.minestom.server.event.trait.PlayerInstanceEvent;
|
||||
import net.minestom.server.inventory.Inventory;
|
||||
import net.minestom.server.inventory.click.ClickType;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.inventory.PlayerInventory;
|
||||
import net.minestom.server.inventory.click.Click;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Called before {@link InventoryClickEvent}, used to potentially cancel the click.
|
||||
*/
|
||||
public class InventoryPreClickEvent implements InventoryEvent, PlayerInstanceEvent, CancellableEvent {
|
||||
|
||||
private final PlayerInventory playerInventory;
|
||||
private final Inventory inventory;
|
||||
private final Player player;
|
||||
private final int slot;
|
||||
private final ClickType clickType;
|
||||
private ItemStack clickedItem;
|
||||
private ItemStack cursorItem;
|
||||
private Click.Info info;
|
||||
|
||||
private boolean cancelled;
|
||||
|
||||
public InventoryPreClickEvent(@Nullable Inventory inventory,
|
||||
@NotNull Player player,
|
||||
int slot, @NotNull ClickType clickType,
|
||||
@NotNull ItemStack clicked, @NotNull ItemStack cursor) {
|
||||
public InventoryPreClickEvent(@NotNull PlayerInventory playerInventory, @NotNull Inventory inventory,
|
||||
@NotNull Player player, @NotNull Click.Info info) {
|
||||
this.playerInventory = playerInventory;
|
||||
this.inventory = inventory;
|
||||
this.player = player;
|
||||
this.slot = slot;
|
||||
this.clickType = clickType;
|
||||
this.clickedItem = clicked;
|
||||
this.cursorItem = cursor;
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -41,66 +34,41 @@ public class InventoryPreClickEvent implements InventoryEvent, PlayerInstanceEve
|
|||
*
|
||||
* @return the player who clicked
|
||||
*/
|
||||
@NotNull
|
||||
public Player getPlayer() {
|
||||
public @NotNull Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the clicked slot number.
|
||||
* Gets the info about the click that occurred. This is enough to fully describe the click.
|
||||
*
|
||||
* @return the clicked slot number
|
||||
* @return the click info
|
||||
*/
|
||||
public int getSlot() {
|
||||
return slot;
|
||||
public @NotNull Click.Info getClickInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the click type.
|
||||
* Updates the information about the click that occurred. This completely overrides the previous click, but it may
|
||||
* require the inventory to be updated.
|
||||
*
|
||||
* @return the click type
|
||||
* @param info the new click info
|
||||
*/
|
||||
@NotNull
|
||||
public ClickType getClickType() {
|
||||
return clickType;
|
||||
public void setClickInfo(@NotNull Click.Info info) {
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the item who have been clicked.
|
||||
* Gets the player inventory that was involved with the click.
|
||||
*
|
||||
* @return the clicked item
|
||||
* @return the player inventory
|
||||
*/
|
||||
@NotNull
|
||||
public ItemStack getClickedItem() {
|
||||
return clickedItem;
|
||||
public @NotNull PlayerInventory getPlayerInventory() {
|
||||
return playerInventory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the clicked item.
|
||||
*
|
||||
* @param clickedItem the clicked item
|
||||
*/
|
||||
public void setClickedItem(@NotNull ItemStack clickedItem) {
|
||||
this.clickedItem = clickedItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the item who was in the player cursor.
|
||||
*
|
||||
* @return the cursor item
|
||||
*/
|
||||
@NotNull
|
||||
public ItemStack getCursorItem() {
|
||||
return cursorItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the cursor item.
|
||||
*
|
||||
* @param cursorItem the cursor item
|
||||
*/
|
||||
public void setCursorItem(@NotNull ItemStack cursorItem) {
|
||||
this.cursorItem = cursorItem;
|
||||
@Override
|
||||
public @NotNull Inventory getInventory() {
|
||||
return inventory;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -112,9 +80,4 @@ public class InventoryPreClickEvent implements InventoryEvent, PlayerInstanceEve
|
|||
public void setCancelled(boolean cancel) {
|
||||
this.cancelled = cancel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Inventory getInventory() {
|
||||
return inventory;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
package net.minestom.server.event.inventory;
|
||||
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.event.trait.PlayerInstanceEvent;
|
||||
import net.minestom.server.inventory.AbstractInventory;
|
||||
import net.minestom.server.inventory.PlayerInventory;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Called when {@link AbstractInventory#safeItemInsert(int, ItemStack)} is being invoked on a {@link PlayerInventory}.
|
||||
* This event cannot be cancelled and items related to the change are already moved.
|
||||
* <p>
|
||||
* When this event is being called, {@link InventoryItemChangeEvent} listeners will also be triggered, so you can
|
||||
* listen only for an ancestor event and check whether it is an instance of that class.
|
||||
*/
|
||||
@SuppressWarnings("JavadocReference")
|
||||
public class PlayerInventoryItemChangeEvent extends InventoryItemChangeEvent implements PlayerInstanceEvent {
|
||||
|
||||
private final Player player;
|
||||
|
||||
public PlayerInventoryItemChangeEvent(@NotNull Player player, int slot, @NotNull ItemStack previousItem, @NotNull ItemStack newItem) {
|
||||
super(null, slot, previousItem, newItem);
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
package net.minestom.server.event.player;
|
||||
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.event.trait.CancellableEvent;
|
||||
import net.minestom.server.event.trait.PlayerInstanceEvent;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Called when a player is trying to swap his main and off hand item.
|
||||
*/
|
||||
public class PlayerSwapItemEvent implements PlayerInstanceEvent, CancellableEvent {
|
||||
|
||||
private final Player player;
|
||||
private ItemStack mainHandItem;
|
||||
private ItemStack offHandItem;
|
||||
|
||||
private boolean cancelled;
|
||||
|
||||
public PlayerSwapItemEvent(@NotNull Player player, @NotNull ItemStack mainHandItem, @NotNull ItemStack offHandItem) {
|
||||
this.player = player;
|
||||
this.mainHandItem = mainHandItem;
|
||||
this.offHandItem = offHandItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the item which will be in player main hand after the event.
|
||||
*
|
||||
* @return the item in main hand
|
||||
*/
|
||||
@NotNull
|
||||
public ItemStack getMainHandItem() {
|
||||
return mainHandItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the item which will be in the player main hand.
|
||||
*
|
||||
* @param mainHandItem the main hand item
|
||||
*/
|
||||
public void setMainHandItem(@NotNull ItemStack mainHandItem) {
|
||||
this.mainHandItem = mainHandItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the item which will be in player off hand after the event.
|
||||
*
|
||||
* @return the item in off hand
|
||||
*/
|
||||
@NotNull
|
||||
public ItemStack getOffHandItem() {
|
||||
return offHandItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the item which will be in the player off hand.
|
||||
*
|
||||
* @param offHandItem the off hand item
|
||||
*/
|
||||
public void setOffHandItem(@NotNull ItemStack offHandItem) {
|
||||
this.offHandItem = offHandItem;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
this.cancelled = cancel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ package net.minestom.server.event.trait;
|
|||
|
||||
import net.minestom.server.event.Event;
|
||||
import net.minestom.server.inventory.Inventory;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Represents any event inside an {@link Inventory}.
|
||||
|
@ -12,7 +12,7 @@ public interface InventoryEvent extends Event {
|
|||
/**
|
||||
* Gets the inventory.
|
||||
*
|
||||
* @return the inventory, null if this is a player's inventory
|
||||
* @return the inventory (may be a player inventory)
|
||||
*/
|
||||
@Nullable Inventory getInventory();
|
||||
@NotNull Inventory getInventory();
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package net.minestom.server.gamedata.tags;
|
|||
import net.minestom.server.entity.EntityType;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.item.Material;
|
||||
import net.minestom.server.registry.ProtocolObject;
|
||||
import net.minestom.server.registry.Registries;
|
||||
import net.minestom.server.registry.Registry;
|
||||
import net.minestom.server.utils.NamespaceID;
|
||||
|
@ -19,14 +20,14 @@ import java.util.function.Function;
|
|||
* Represents a group of items, blocks, fluids, entity types or function.
|
||||
* Immutable by design
|
||||
*/
|
||||
public final class Tag {
|
||||
public final class Tag implements ProtocolObject {
|
||||
private final NamespaceID name;
|
||||
private final Set<NamespaceID> values;
|
||||
|
||||
/**
|
||||
* Creates a new empty tag. This does not cache the tag.
|
||||
*/
|
||||
public Tag(NamespaceID name) {
|
||||
public Tag(@NotNull NamespaceID name) {
|
||||
this.name = name;
|
||||
this.values = new HashSet<>();
|
||||
}
|
||||
|
@ -34,7 +35,7 @@ public final class Tag {
|
|||
/**
|
||||
* Creates a new tag with the given values. This does not cache the tag.
|
||||
*/
|
||||
public Tag(NamespaceID name, Set<NamespaceID> values) {
|
||||
public Tag(@NotNull NamespaceID name, @NotNull Set<NamespaceID> values) {
|
||||
this.name = name;
|
||||
this.values = new HashSet<>(values);
|
||||
}
|
||||
|
@ -45,7 +46,7 @@ public final class Tag {
|
|||
* @param id the id to check against
|
||||
* @return 'true' iif this tag contains the given id
|
||||
*/
|
||||
public boolean contains(NamespaceID id) {
|
||||
public boolean contains(@NotNull NamespaceID id) {
|
||||
return values.contains(id);
|
||||
}
|
||||
|
||||
|
@ -54,13 +55,19 @@ public final class Tag {
|
|||
*
|
||||
* @return immutable set of values present in this tag
|
||||
*/
|
||||
public Set<NamespaceID> getValues() {
|
||||
public @NotNull Set<NamespaceID> getValues() {
|
||||
return Collections.unmodifiableSet(values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull NamespaceID namespace() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of this tag
|
||||
*/
|
||||
@Deprecated
|
||||
public NamespaceID getName() {
|
||||
return name;
|
||||
}
|
||||
|
|
|
@ -1,480 +0,0 @@
|
|||
package net.minestom.server.instance;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntIntImmutablePair;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.instance.block.BlockHandler;
|
||||
import net.minestom.server.utils.NamespaceID;
|
||||
import net.minestom.server.utils.async.AsyncUtils;
|
||||
import net.minestom.server.world.biomes.Biome;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jglrxavpok.hephaistos.mca.*;
|
||||
import org.jglrxavpok.hephaistos.mca.readers.ChunkReader;
|
||||
import org.jglrxavpok.hephaistos.mca.readers.ChunkSectionReader;
|
||||
import org.jglrxavpok.hephaistos.mca.readers.SectionBiomeInformation;
|
||||
import org.jglrxavpok.hephaistos.mca.writer.ChunkSectionWriter;
|
||||
import org.jglrxavpok.hephaistos.mca.writer.ChunkWriter;
|
||||
import org.jglrxavpok.hephaistos.nbt.*;
|
||||
import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class AnvilLoader implements IChunkLoader {
|
||||
private final static Logger LOGGER = LoggerFactory.getLogger(AnvilLoader.class);
|
||||
private final static Biome PLAINS = MinecraftServer.getBiomeManager().getByName(NamespaceID.from("minecraft:plains"));
|
||||
|
||||
private final Map<String, RegionFile> alreadyLoaded = new ConcurrentHashMap<>();
|
||||
private final Path path;
|
||||
private final Path levelPath;
|
||||
private final Path regionPath;
|
||||
|
||||
private static class RegionCache extends ConcurrentHashMap<IntIntImmutablePair, Set<IntIntImmutablePair>> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the chunks currently loaded per region. Used to determine when a region file can be unloaded.
|
||||
*/
|
||||
private final RegionCache perRegionLoadedChunks = new RegionCache();
|
||||
|
||||
// thread local to avoid contention issues with locks
|
||||
private final ThreadLocal<Int2ObjectMap<BlockState>> blockStateId2ObjectCacheTLS = ThreadLocal.withInitial(Int2ObjectArrayMap::new);
|
||||
|
||||
public AnvilLoader(@NotNull Path path) {
|
||||
this.path = path;
|
||||
this.levelPath = path.resolve("level.dat");
|
||||
this.regionPath = path.resolve("region");
|
||||
}
|
||||
|
||||
public AnvilLoader(@NotNull String path) {
|
||||
this(Path.of(path));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadInstance(@NotNull Instance instance) {
|
||||
if (!Files.exists(levelPath)) {
|
||||
return;
|
||||
}
|
||||
try (var reader = new NBTReader(Files.newInputStream(levelPath))) {
|
||||
final NBTCompound tag = (NBTCompound) reader.read();
|
||||
Files.copy(levelPath, path.resolve("level.dat_old"), StandardCopyOption.REPLACE_EXISTING);
|
||||
instance.tagHandler().updateContent(tag);
|
||||
} catch (IOException | NBTException e) {
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull CompletableFuture<@Nullable Chunk> loadChunk(@NotNull Instance instance, int chunkX, int chunkZ) {
|
||||
if (!Files.exists(path)) {
|
||||
// No world folder
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
try {
|
||||
return loadMCA(instance, chunkX, chunkZ);
|
||||
} catch (Exception e) {
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
}
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
private @NotNull CompletableFuture<@Nullable Chunk> loadMCA(Instance instance, int chunkX, int chunkZ) throws IOException, AnvilException {
|
||||
final RegionFile mcaFile = getMCAFile(instance, chunkX, chunkZ);
|
||||
if (mcaFile == null)
|
||||
return CompletableFuture.completedFuture(null);
|
||||
final NBTCompound chunkData = mcaFile.getChunkData(chunkX, chunkZ);
|
||||
if (chunkData == null)
|
||||
return CompletableFuture.completedFuture(null);
|
||||
|
||||
final ChunkReader chunkReader = new ChunkReader(chunkData);
|
||||
|
||||
Chunk chunk = instance.getChunkSupplier().createChunk(instance, chunkX, chunkZ);
|
||||
synchronized (chunk) {
|
||||
var yRange = chunkReader.getYRange();
|
||||
if (yRange.getStart() < instance.getDimensionType().getMinY()) {
|
||||
throw new AnvilException(
|
||||
String.format("Trying to load chunk with minY = %d, but instance dimension type (%s) has a minY of %d",
|
||||
yRange.getStart(),
|
||||
instance.getDimensionType().getName().asString(),
|
||||
instance.getDimensionType().getMinY()
|
||||
));
|
||||
}
|
||||
if (yRange.getEndInclusive() > instance.getDimensionType().getMaxY()) {
|
||||
throw new AnvilException(
|
||||
String.format("Trying to load chunk with maxY = %d, but instance dimension type (%s) has a maxY of %d",
|
||||
yRange.getEndInclusive(),
|
||||
instance.getDimensionType().getName().asString(),
|
||||
instance.getDimensionType().getMaxY()
|
||||
));
|
||||
}
|
||||
|
||||
// TODO: Parallelize block, block entities and biome loading
|
||||
// Blocks + Biomes
|
||||
loadSections(chunk, chunkReader);
|
||||
|
||||
// Block entities
|
||||
loadBlockEntities(chunk, chunkReader);
|
||||
}
|
||||
synchronized (perRegionLoadedChunks) {
|
||||
int regionX = CoordinatesKt.chunkToRegion(chunkX);
|
||||
int regionZ = CoordinatesKt.chunkToRegion(chunkZ);
|
||||
var chunks = perRegionLoadedChunks.computeIfAbsent(new IntIntImmutablePair(regionX, regionZ), r -> new HashSet<>()); // region cache may have been removed on another thread due to unloadChunk
|
||||
chunks.add(new IntIntImmutablePair(chunkX, chunkZ));
|
||||
}
|
||||
return CompletableFuture.completedFuture(chunk);
|
||||
}
|
||||
|
||||
private @Nullable RegionFile getMCAFile(Instance instance, int chunkX, int chunkZ) {
|
||||
final int regionX = CoordinatesKt.chunkToRegion(chunkX);
|
||||
final int regionZ = CoordinatesKt.chunkToRegion(chunkZ);
|
||||
return alreadyLoaded.computeIfAbsent(RegionFile.Companion.createFileName(regionX, regionZ), n -> {
|
||||
try {
|
||||
final Path regionPath = this.regionPath.resolve(n);
|
||||
if (!Files.exists(regionPath)) {
|
||||
return null;
|
||||
}
|
||||
synchronized (perRegionLoadedChunks) {
|
||||
Set<IntIntImmutablePair> previousVersion = perRegionLoadedChunks.put(new IntIntImmutablePair(regionX, regionZ), new HashSet<>());
|
||||
assert previousVersion == null : "The AnvilLoader cache should not already have data for this region.";
|
||||
}
|
||||
return new RegionFile(new RandomAccessFile(regionPath.toFile(), "rw"), regionX, regionZ, instance.getDimensionType().getMinY(), instance.getDimensionType().getMaxY() - 1);
|
||||
} catch (IOException | AnvilException e) {
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadSections(Chunk chunk, ChunkReader chunkReader) {
|
||||
final HashMap<String, Biome> biomeCache = new HashMap<>();
|
||||
for (NBTCompound sectionNBT : chunkReader.getSections()) {
|
||||
ChunkSectionReader sectionReader = new ChunkSectionReader(chunkReader.getMinecraftVersion(), sectionNBT);
|
||||
|
||||
if (sectionReader.isSectionEmpty()) continue;
|
||||
final int sectionY = sectionReader.getY();
|
||||
final int yOffset = Chunk.CHUNK_SECTION_SIZE * sectionY;
|
||||
|
||||
Section section = chunk.getSection(sectionY);
|
||||
|
||||
if (sectionReader.getSkyLight() != null) {
|
||||
section.setSkyLight(sectionReader.getSkyLight().copyArray());
|
||||
}
|
||||
if (sectionReader.getBlockLight() != null) {
|
||||
section.setBlockLight(sectionReader.getBlockLight().copyArray());
|
||||
}
|
||||
|
||||
// Biomes
|
||||
if (chunkReader.getGenerationStatus().compareTo(ChunkColumn.GenerationStatus.Biomes) > 0) {
|
||||
SectionBiomeInformation sectionBiomeInformation = chunkReader.readSectionBiomes(sectionReader);
|
||||
|
||||
if (sectionBiomeInformation != null && sectionBiomeInformation.hasBiomeInformation()) {
|
||||
if (sectionBiomeInformation.isFilledWithSingleBiome()) {
|
||||
for (int y = 0; y < Chunk.CHUNK_SECTION_SIZE; y++) {
|
||||
for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z++) {
|
||||
for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) {
|
||||
int finalX = chunk.chunkX * Chunk.CHUNK_SIZE_X + x;
|
||||
int finalZ = chunk.chunkZ * Chunk.CHUNK_SIZE_Z + z;
|
||||
int finalY = sectionY * Chunk.CHUNK_SECTION_SIZE + y;
|
||||
String biomeName = sectionBiomeInformation.getBaseBiome();
|
||||
Biome biome = biomeCache.computeIfAbsent(biomeName, n ->
|
||||
Objects.requireNonNullElse(MinecraftServer.getBiomeManager().getByName(NamespaceID.from(n)), PLAINS));
|
||||
chunk.setBiome(finalX, finalY, finalZ, biome);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int y = 0; y < Chunk.CHUNK_SECTION_SIZE; y++) {
|
||||
for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z++) {
|
||||
for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) {
|
||||
int finalX = chunk.chunkX * Chunk.CHUNK_SIZE_X + x;
|
||||
int finalZ = chunk.chunkZ * Chunk.CHUNK_SIZE_Z + z;
|
||||
int finalY = sectionY * Chunk.CHUNK_SECTION_SIZE + y;
|
||||
|
||||
int index = x / 4 + (z / 4) * 4 + (y / 4) * 16;
|
||||
String biomeName = sectionBiomeInformation.getBiomes()[index];
|
||||
Biome biome = biomeCache.computeIfAbsent(biomeName, n ->
|
||||
Objects.requireNonNullElse(MinecraftServer.getBiomeManager().getByName(NamespaceID.from(n)), PLAINS));
|
||||
chunk.setBiome(finalX, finalY, finalZ, biome);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Blocks
|
||||
final NBTList<NBTCompound> blockPalette = sectionReader.getBlockPalette();
|
||||
if (blockPalette != null) {
|
||||
final int[] blockStateIndices = sectionReader.getUncompressedBlockStateIDs();
|
||||
Block[] convertedPalette = new Block[blockPalette.getSize()];
|
||||
for (int i = 0; i < convertedPalette.length; i++) {
|
||||
final NBTCompound paletteEntry = blockPalette.get(i);
|
||||
String blockName = Objects.requireNonNull(paletteEntry.getString("Name"));
|
||||
if (blockName.equals("minecraft:air")) {
|
||||
convertedPalette[i] = Block.AIR;
|
||||
} else {
|
||||
if (blockName.equals("minecraft:grass")) {
|
||||
blockName = "minecraft:short_grass";
|
||||
}
|
||||
Block block = Objects.requireNonNull(Block.fromNamespaceId(blockName));
|
||||
// Properties
|
||||
final Map<String, String> properties = new HashMap<>();
|
||||
NBTCompound propertiesNBT = paletteEntry.getCompound("Properties");
|
||||
if (propertiesNBT != null) {
|
||||
for (var property : propertiesNBT) {
|
||||
if (property.getValue().getID() != NBTType.TAG_String) {
|
||||
LOGGER.warn("Fail to parse block state properties {}, expected a TAG_String for {}, but contents were {}",
|
||||
propertiesNBT,
|
||||
property.getKey(),
|
||||
property.getValue().toSNBT());
|
||||
} else {
|
||||
properties.put(property.getKey(), ((NBTString) property.getValue()).getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!properties.isEmpty()) block = block.withProperties(properties);
|
||||
// Handler
|
||||
final BlockHandler handler = MinecraftServer.getBlockManager().getHandler(block.name());
|
||||
if (handler != null) block = block.withHandler(handler);
|
||||
|
||||
convertedPalette[i] = block;
|
||||
}
|
||||
}
|
||||
|
||||
for (int y = 0; y < Chunk.CHUNK_SECTION_SIZE; y++) {
|
||||
for (int z = 0; z < Chunk.CHUNK_SECTION_SIZE; z++) {
|
||||
for (int x = 0; x < Chunk.CHUNK_SECTION_SIZE; x++) {
|
||||
try {
|
||||
final int blockIndex = y * Chunk.CHUNK_SECTION_SIZE * Chunk.CHUNK_SECTION_SIZE + z * Chunk.CHUNK_SECTION_SIZE + x;
|
||||
final int paletteIndex = blockStateIndices[blockIndex];
|
||||
final Block block = convertedPalette[paletteIndex];
|
||||
|
||||
chunk.setBlock(x, y + yOffset, z, block);
|
||||
} catch (Exception e) {
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadBlockEntities(Chunk loadedChunk, ChunkReader chunkReader) {
|
||||
for (NBTCompound te : chunkReader.getBlockEntities()) {
|
||||
final var x = te.getInt("x");
|
||||
final var y = te.getInt("y");
|
||||
final var z = te.getInt("z");
|
||||
if (x == null || y == null || z == null) {
|
||||
LOGGER.warn("Tile entity has failed to load due to invalid coordinate");
|
||||
continue;
|
||||
}
|
||||
Block block = loadedChunk.getBlock(x, y, z);
|
||||
|
||||
final String tileEntityID = te.getString("id");
|
||||
if (tileEntityID != null) {
|
||||
final BlockHandler handler = MinecraftServer.getBlockManager().getHandlerOrDummy(tileEntityID);
|
||||
block = block.withHandler(handler);
|
||||
}
|
||||
// Remove anvil tags
|
||||
MutableNBTCompound mutableCopy = te.toMutableCompound();
|
||||
mutableCopy.remove("id");
|
||||
mutableCopy.remove("x");
|
||||
mutableCopy.remove("y");
|
||||
mutableCopy.remove("z");
|
||||
mutableCopy.remove("keepPacked");
|
||||
// Place block
|
||||
final var finalBlock = mutableCopy.getSize() > 0 ?
|
||||
block.withNbt(mutableCopy.toCompound()) : block;
|
||||
loadedChunk.setBlock(x, y, z, finalBlock);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull CompletableFuture<Void> saveInstance(@NotNull Instance instance) {
|
||||
final NBTCompound nbt = instance.tagHandler().asCompound();
|
||||
if (nbt.isEmpty()) {
|
||||
// Instance has no data
|
||||
return AsyncUtils.VOID_FUTURE;
|
||||
}
|
||||
try (NBTWriter writer = new NBTWriter(Files.newOutputStream(levelPath))) {
|
||||
writer.writeNamed("", nbt);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return AsyncUtils.VOID_FUTURE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull CompletableFuture<Void> saveChunk(@NotNull Chunk chunk) {
|
||||
final int chunkX = chunk.getChunkX();
|
||||
final int chunkZ = chunk.getChunkZ();
|
||||
RegionFile mcaFile;
|
||||
synchronized (alreadyLoaded) {
|
||||
mcaFile = getMCAFile(chunk.instance, chunkX, chunkZ);
|
||||
if (mcaFile == null) {
|
||||
final int regionX = CoordinatesKt.chunkToRegion(chunkX);
|
||||
final int regionZ = CoordinatesKt.chunkToRegion(chunkZ);
|
||||
final String n = RegionFile.Companion.createFileName(regionX, regionZ);
|
||||
File regionFile = new File(regionPath.toFile(), n);
|
||||
try {
|
||||
if (!regionFile.exists()) {
|
||||
if (!regionFile.getParentFile().exists()) {
|
||||
regionFile.getParentFile().mkdirs();
|
||||
}
|
||||
regionFile.createNewFile();
|
||||
}
|
||||
mcaFile = new RegionFile(new RandomAccessFile(regionFile, "rw"), regionX, regionZ);
|
||||
alreadyLoaded.put(n, mcaFile);
|
||||
} catch (AnvilException | IOException e) {
|
||||
LOGGER.error("Failed to save chunk " + chunkX + ", " + chunkZ, e);
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
return AsyncUtils.VOID_FUTURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
ChunkWriter writer = new ChunkWriter(SupportedVersion.Companion.getLatest());
|
||||
save(chunk, writer);
|
||||
try {
|
||||
LOGGER.debug("Attempt saving at {} {}", chunk.getChunkX(), chunk.getChunkZ());
|
||||
mcaFile.writeColumnData(writer.toNBT(), chunk.getChunkX(), chunk.getChunkZ());
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Failed to save chunk " + chunkX + ", " + chunkZ, e);
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
return AsyncUtils.VOID_FUTURE;
|
||||
}
|
||||
return AsyncUtils.VOID_FUTURE;
|
||||
}
|
||||
|
||||
private BlockState getBlockState(final Block block) {
|
||||
return blockStateId2ObjectCacheTLS.get().computeIfAbsent(block.stateId(), _unused -> new BlockState(block.name(), block.properties()));
|
||||
}
|
||||
|
||||
private void save(Chunk chunk, ChunkWriter chunkWriter) {
|
||||
final int minY = chunk.getMinSection() * Chunk.CHUNK_SECTION_SIZE;
|
||||
final int maxY = chunk.getMaxSection() * Chunk.CHUNK_SECTION_SIZE - 1;
|
||||
chunkWriter.setYPos(minY);
|
||||
List<NBTCompound> blockEntities = new ArrayList<>();
|
||||
chunkWriter.setStatus(ChunkColumn.GenerationStatus.Full);
|
||||
|
||||
List<NBTCompound> sectionData = new ArrayList<>((maxY - minY + 1) / Chunk.CHUNK_SECTION_SIZE);
|
||||
int[] palettedBiomes = new int[ChunkSection.Companion.getBiomeArraySize()];
|
||||
int[] palettedBlockStates = new int[Chunk.CHUNK_SIZE_X * Chunk.CHUNK_SECTION_SIZE * Chunk.CHUNK_SIZE_Z];
|
||||
for (int sectionY = chunk.getMinSection(); sectionY < chunk.getMaxSection(); sectionY++) {
|
||||
ChunkSectionWriter sectionWriter = new ChunkSectionWriter(SupportedVersion.Companion.getLatest(), (byte) sectionY);
|
||||
|
||||
Section section = chunk.getSection(sectionY);
|
||||
sectionWriter.setSkyLights(section.skyLight().array());
|
||||
sectionWriter.setBlockLights(section.blockLight().array());
|
||||
|
||||
BiomePalette biomePalette = new BiomePalette();
|
||||
BlockPalette blockPalette = new BlockPalette();
|
||||
for (int sectionLocalY = 0; sectionLocalY < Chunk.CHUNK_SECTION_SIZE; sectionLocalY++) {
|
||||
for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z++) {
|
||||
for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) {
|
||||
final int y = sectionLocalY + sectionY * Chunk.CHUNK_SECTION_SIZE;
|
||||
|
||||
final int blockIndex = x + sectionLocalY * 16 * 16 + z * 16;
|
||||
|
||||
final Block block = chunk.getBlock(x, y, z);
|
||||
|
||||
final BlockState hephaistosBlockState = getBlockState(block);
|
||||
blockPalette.increaseReference(hephaistosBlockState);
|
||||
|
||||
palettedBlockStates[blockIndex] = blockPalette.getPaletteIndex(hephaistosBlockState);
|
||||
|
||||
// biome are stored for 4x4x4 volumes, avoid unnecessary work
|
||||
if (x % 4 == 0 && sectionLocalY % 4 == 0 && z % 4 == 0) {
|
||||
int biomeIndex = (x / 4) + (sectionLocalY / 4) * 4 * 4 + (z / 4) * 4;
|
||||
final Biome biome = chunk.getBiome(x, y, z);
|
||||
final String biomeName = biome.name();
|
||||
|
||||
biomePalette.increaseReference(biomeName);
|
||||
palettedBiomes[biomeIndex] = biomePalette.getPaletteIndex(biomeName);
|
||||
}
|
||||
|
||||
// Block entities
|
||||
final BlockHandler handler = block.handler();
|
||||
final NBTCompound originalNBT = block.nbt();
|
||||
if (originalNBT != null || handler != null) {
|
||||
MutableNBTCompound nbt = originalNBT != null ?
|
||||
originalNBT.toMutableCompound() : new MutableNBTCompound();
|
||||
|
||||
if (handler != null) {
|
||||
nbt.setString("id", handler.getNamespaceId().asString());
|
||||
}
|
||||
nbt.setInt("x", x + Chunk.CHUNK_SIZE_X * chunk.getChunkX());
|
||||
nbt.setInt("y", y);
|
||||
nbt.setInt("z", z + Chunk.CHUNK_SIZE_Z * chunk.getChunkZ());
|
||||
nbt.setByte("keepPacked", (byte) 0);
|
||||
blockEntities.add(nbt.toCompound());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sectionWriter.setPalettedBiomes(biomePalette, palettedBiomes);
|
||||
sectionWriter.setPalettedBlockStates(blockPalette, palettedBlockStates);
|
||||
|
||||
sectionData.add(sectionWriter.toNBT());
|
||||
}
|
||||
|
||||
chunkWriter.setSectionsData(NBT.List(NBTType.TAG_Compound, sectionData));
|
||||
chunkWriter.setBlockEntityData(NBT.List(NBTType.TAG_Compound, blockEntities));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unload a given chunk. Also unloads a region when no chunk from that region is loaded.
|
||||
*
|
||||
* @param chunk the chunk to unload
|
||||
*/
|
||||
@Override
|
||||
public void unloadChunk(Chunk chunk) {
|
||||
final int regionX = CoordinatesKt.chunkToRegion(chunk.chunkX);
|
||||
final int regionZ = CoordinatesKt.chunkToRegion(chunk.chunkZ);
|
||||
|
||||
final IntIntImmutablePair regionKey = new IntIntImmutablePair(regionX, regionZ);
|
||||
synchronized (perRegionLoadedChunks) {
|
||||
Set<IntIntImmutablePair> chunks = perRegionLoadedChunks.get(regionKey);
|
||||
if (chunks != null) { // if null, trying to unload a chunk from a region that was not created by the AnvilLoader
|
||||
// don't check return value, trying to unload a chunk not created by the AnvilLoader is valid
|
||||
chunks.remove(new IntIntImmutablePair(chunk.chunkX, chunk.chunkZ));
|
||||
|
||||
if (chunks.isEmpty()) {
|
||||
perRegionLoadedChunks.remove(regionKey);
|
||||
RegionFile regionFile = alreadyLoaded.remove(RegionFile.Companion.createFileName(regionX, regionZ));
|
||||
if (regionFile != null) {
|
||||
try {
|
||||
regionFile.close();
|
||||
} catch (IOException e) {
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsParallelLoading() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsParallelSaving() {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ import net.minestom.server.entity.Player;
|
|||
import net.minestom.server.entity.pathfinding.PFColumnarSpace;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.instance.block.BlockHandler;
|
||||
import net.minestom.server.instance.generator.Generator;
|
||||
import net.minestom.server.network.packet.server.SendablePacket;
|
||||
import net.minestom.server.network.packet.server.play.ChunkDataPacket;
|
||||
import net.minestom.server.snapshot.Snapshotable;
|
||||
|
@ -227,11 +228,11 @@ public abstract class Chunk implements Block.Getter, Block.Setter, Biome.Getter,
|
|||
}
|
||||
|
||||
/**
|
||||
* Gets if this chunk will or had been loaded with a {@link ChunkGenerator}.
|
||||
* Gets if this chunk will or had been loaded with a {@link Generator}.
|
||||
* <p>
|
||||
* If false, the chunk will be entirely empty when loaded.
|
||||
*
|
||||
* @return true if this chunk is affected by a {@link ChunkGenerator}
|
||||
* @return true if this chunk is affected by a {@link Generator}
|
||||
*/
|
||||
public boolean shouldGenerate() {
|
||||
return shouldGenerate;
|
||||
|
@ -241,7 +242,7 @@ public abstract class Chunk implements Block.Getter, Block.Setter, Biome.Getter,
|
|||
* Gets if this chunk is read-only.
|
||||
* <p>
|
||||
* Being read-only should prevent block placing/breaking and setting block from an {@link Instance}.
|
||||
* It does not affect {@link IChunkLoader} and {@link ChunkGenerator}.
|
||||
* It does not affect {@link IChunkLoader} and {@link Generator}.
|
||||
*
|
||||
* @return true if the chunk is read-only
|
||||
*/
|
||||
|
@ -253,7 +254,7 @@ public abstract class Chunk implements Block.Getter, Block.Setter, Biome.Getter,
|
|||
* Changes the read state of the chunk.
|
||||
* <p>
|
||||
* Being read-only should prevent block placing/breaking and setting block from an {@link Instance}.
|
||||
* It does not affect {@link IChunkLoader} and {@link ChunkGenerator}.
|
||||
* It does not affect {@link IChunkLoader} and {@link Generator}.
|
||||
*
|
||||
* @param readOnly true to make the chunk read-only, false otherwise
|
||||
*/
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
package net.minestom.server.instance;
|
||||
|
||||
import net.minestom.server.instance.batch.ChunkBatch;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Responsible for the {@link Chunk} generation, can be set using {@link Instance#setChunkGenerator(ChunkGenerator)}.
|
||||
* <p>
|
||||
* Called if the instance {@link IChunkLoader} hasn't been able to load the chunk.
|
||||
* @deprecated Replaced by {@link net.minestom.server.instance.generator.Generator}
|
||||
*/
|
||||
@Deprecated
|
||||
public interface ChunkGenerator {
|
||||
|
||||
/**
|
||||
* Called when the blocks in the {@link Chunk} should be set using {@link ChunkBatch#setBlock(int, int, int, Block)}
|
||||
* or similar.
|
||||
* <p>
|
||||
* WARNING: all positions are chunk-based (0-15).
|
||||
*
|
||||
* @param batch the {@link ChunkBatch} which will be flush after the generation
|
||||
* @param chunkX the chunk X
|
||||
* @param chunkZ the chunk Z
|
||||
*/
|
||||
void generateChunkData(@NotNull ChunkBatch batch, int chunkX, int chunkZ);
|
||||
|
||||
/**
|
||||
* Gets all the {@link ChunkPopulator} of this generator.
|
||||
*
|
||||
* @return a {@link List} of {@link ChunkPopulator}, can be null or empty
|
||||
*/
|
||||
@Nullable
|
||||
List<ChunkPopulator> getPopulators();
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
package net.minestom.server.instance;
|
||||
|
||||
import net.minestom.server.coordinate.Point;
|
||||
import net.minestom.server.instance.batch.ChunkBatch;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.instance.generator.GenerationUnit;
|
||||
import net.minestom.server.instance.generator.Generator;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Provides full compatibility for the deprecated {@link ChunkGenerator}
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
record ChunkGeneratorCompatibilityLayer(@NotNull ChunkGenerator chunkGenerator) implements Generator {
|
||||
@Override
|
||||
public void generate(@NotNull GenerationUnit unit) {
|
||||
if (!(unit instanceof GeneratorImpl.UnitImpl impl) ||
|
||||
!(impl.modifier() instanceof GeneratorImpl.AreaModifierImpl modifier && modifier.chunk() != null)) {
|
||||
throw new IllegalArgumentException("Invalid unit");
|
||||
}
|
||||
|
||||
final int startY = unit.absoluteStart().blockY();
|
||||
ChunkBatch batch = new ChunkBatch() {
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull Block block) {
|
||||
unit.modifier().setRelative(x, y - startY, z, block);
|
||||
}
|
||||
};
|
||||
final Point start = unit.absoluteStart();
|
||||
chunkGenerator.generateChunkData(batch, start.chunkX(), start.chunkZ());
|
||||
|
||||
final List<ChunkPopulator> populators = chunkGenerator.getPopulators();
|
||||
final boolean hasPopulator = populators != null && !populators.isEmpty();
|
||||
if (hasPopulator) {
|
||||
for (ChunkPopulator chunkPopulator : populators) {
|
||||
chunkPopulator.populateChunk(batch, modifier.chunk());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
package net.minestom.server.instance;
|
||||
|
||||
import net.minestom.server.instance.batch.ChunkBatch;
|
||||
|
||||
@Deprecated
|
||||
public interface ChunkPopulator {
|
||||
|
||||
void populateChunk(ChunkBatch batch, Chunk chunk);
|
||||
|
||||
}
|
|
@ -2,6 +2,7 @@ package net.minestom.server.instance;
|
|||
|
||||
import com.extollit.gaming.ai.path.model.ColumnarOcclusionFieldList;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.coordinate.Point;
|
||||
import net.minestom.server.coordinate.Vec;
|
||||
|
@ -27,8 +28,6 @@ import net.minestom.server.world.biomes.Biome;
|
|||
import net.minestom.server.world.biomes.BiomeManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBT;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -225,7 +224,7 @@ public class DynamicChunk extends Chunk {
|
|||
}
|
||||
|
||||
private @NotNull ChunkDataPacket createChunkPacket() {
|
||||
final NBTCompound heightmapsNBT = computeHeightmap();
|
||||
final CompoundBinaryTag heightmapsNBT = computeHeightmap();
|
||||
// Data
|
||||
|
||||
final byte[] data;
|
||||
|
@ -242,7 +241,7 @@ public class DynamicChunk extends Chunk {
|
|||
);
|
||||
}
|
||||
|
||||
protected NBTCompound computeHeightmap() {
|
||||
protected CompoundBinaryTag computeHeightmap() {
|
||||
// TODO: don't hardcode heightmaps
|
||||
// Heightmap
|
||||
int dimensionHeight = getInstance().getDimensionType().getHeight();
|
||||
|
@ -255,9 +254,10 @@ public class DynamicChunk extends Chunk {
|
|||
}
|
||||
}
|
||||
final int bitsForHeight = MathUtils.bitsToRepresent(dimensionHeight);
|
||||
return NBT.Compound(Map.of(
|
||||
"MOTION_BLOCKING", NBT.LongArray(encodeBlocks(motionBlocking, bitsForHeight)),
|
||||
"WORLD_SURFACE", NBT.LongArray(encodeBlocks(worldSurface, bitsForHeight))));
|
||||
return CompoundBinaryTag.builder()
|
||||
.putLongArray("MOTION_BLOCKING", encodeBlocks(motionBlocking, bitsForHeight))
|
||||
.putLongArray("WORLD_SURFACE", encodeBlocks(worldSurface, bitsForHeight))
|
||||
.build();
|
||||
}
|
||||
|
||||
@NotNull UpdateLightPacket createLightPacket() {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package net.minestom.server.instance;
|
||||
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ExplosionSupplier {
|
||||
|
@ -12,9 +12,9 @@ public interface ExplosionSupplier {
|
|||
* @param centerY center Y of the explosion
|
||||
* @param centerZ center Z of the explosion
|
||||
* @param strength strength of the explosion
|
||||
* @param additionalData data passed via {@link Instance#explode(float, float, float, float, NBTCompound)} )}. Can be null
|
||||
* @param additionalData data passed via {@link Instance#explode(float, float, float, float, CompoundBinaryTag)} )}. Can be null
|
||||
* @return Explosion object representing the algorithm to use
|
||||
*/
|
||||
Explosion createExplosion(float centerX, float centerY, float centerZ, float strength, NBTCompound additionalData);
|
||||
Explosion createExplosion(float centerX, float centerY, float centerZ, float strength, CompoundBinaryTag additionalData);
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package net.minestom.server.instance;
|
||||
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.instance.anvil.AnvilLoader;
|
||||
import net.minestom.server.utils.async.AsyncUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
@ -27,7 +28,7 @@ public interface IChunkLoader {
|
|||
}
|
||||
|
||||
/**
|
||||
* Loads a {@link Chunk}, all blocks should be set since the {@link ChunkGenerator} is not applied.
|
||||
* Loads a {@link Chunk}, all blocks should be set since the {@link net.minestom.server.instance.generator.Generator} is not applied.
|
||||
*
|
||||
* @param instance the {@link Instance} where the {@link Chunk} belong
|
||||
* @param chunkX the chunk X
|
||||
|
|
|
@ -2,6 +2,7 @@ package net.minestom.server.instance;
|
|||
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArraySet;
|
||||
import net.kyori.adventure.identity.Identity;
|
||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||
import net.kyori.adventure.pointer.Pointers;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.ServerProcess;
|
||||
|
@ -45,7 +46,6 @@ import net.minestom.server.world.DimensionType;
|
|||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
|
@ -205,7 +205,7 @@ public abstract class Instance implements Block.Getter, Block.Setter,
|
|||
public abstract boolean breakBlock(@NotNull Player player, @NotNull Point blockPosition, @NotNull BlockFace blockFace, boolean doBlockUpdates);
|
||||
|
||||
/**
|
||||
* Forces the generation of a {@link Chunk}, even if no file and {@link ChunkGenerator} are defined.
|
||||
* Forces the generation of a {@link Chunk}, even if no file and {@link Generator} are defined.
|
||||
*
|
||||
* @param chunkX the chunk X
|
||||
* @param chunkZ the chunk Z
|
||||
|
@ -318,17 +318,6 @@ public abstract class Instance implements Block.Getter, Block.Setter,
|
|||
*/
|
||||
public abstract @NotNull CompletableFuture<Void> saveChunksToStorage();
|
||||
|
||||
/**
|
||||
* Changes the instance {@link ChunkGenerator}.
|
||||
*
|
||||
* @param chunkGenerator the new {@link ChunkGenerator} of the instance
|
||||
* @deprecated Use {@link #setGenerator(Generator)}
|
||||
*/
|
||||
@Deprecated
|
||||
public void setChunkGenerator(@Nullable ChunkGenerator chunkGenerator) {
|
||||
setGenerator(chunkGenerator != null ? new ChunkGeneratorCompatibilityLayer(chunkGenerator) : null);
|
||||
}
|
||||
|
||||
public abstract void setChunkSupplier(@NotNull ChunkSupplier chunkSupplier);
|
||||
|
||||
/**
|
||||
|
@ -792,7 +781,7 @@ public abstract class Instance implements Block.Getter, Block.Setter,
|
|||
* @param additionalData data to pass to the explosion supplier
|
||||
* @throws IllegalStateException If no {@link ExplosionSupplier} was supplied
|
||||
*/
|
||||
public void explode(float centerX, float centerY, float centerZ, float strength, @Nullable NBTCompound additionalData) {
|
||||
public void explode(float centerX, float centerY, float centerZ, float strength, @Nullable CompoundBinaryTag additionalData) {
|
||||
final ExplosionSupplier explosionSupplier = getExplosionSupplier();
|
||||
Check.stateCondition(explosionSupplier == null, "Tried to create an explosion with no explosion supplier");
|
||||
final Explosion explosion = explosionSupplier.createExplosion(centerX, centerY, centerZ, strength, additionalData);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package net.minestom.server.instance;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
|
||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.coordinate.Point;
|
||||
import net.minestom.server.coordinate.Vec;
|
||||
|
@ -10,6 +11,7 @@ import net.minestom.server.event.EventDispatcher;
|
|||
import net.minestom.server.event.instance.InstanceChunkLoadEvent;
|
||||
import net.minestom.server.event.instance.InstanceChunkUnloadEvent;
|
||||
import net.minestom.server.event.player.PlayerBlockBreakEvent;
|
||||
import net.minestom.server.instance.anvil.AnvilLoader;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.instance.block.BlockFace;
|
||||
import net.minestom.server.instance.block.BlockHandler;
|
||||
|
@ -32,7 +34,6 @@ import net.minestom.server.world.DimensionType;
|
|||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import space.vectrix.flare.fastutil.Long2ObjectSyncMap;
|
||||
|
@ -158,7 +159,7 @@ public class InstanceContainer extends Instance {
|
|||
this, block, pp.getBlockFace(), blockPosition,
|
||||
new Vec(pp.getCursorX(), pp.getCursorY(), pp.getCursorZ()),
|
||||
pp.getPlayer().getPosition(),
|
||||
pp.getPlayer().getItemInHand(pp.getHand()).meta(),
|
||||
pp.getPlayer().getItemInHand(pp.getHand()),
|
||||
pp.getPlayer().isSneaking()
|
||||
);
|
||||
} else {
|
||||
|
@ -186,7 +187,7 @@ public class InstanceContainer extends Instance {
|
|||
chunk.sendPacketToViewers(new BlockChangePacket(blockPosition, block.stateId()));
|
||||
var registry = block.registry();
|
||||
if (registry.isBlockEntity()) {
|
||||
final NBTCompound data = BlockUtils.extractClientNbt(block);
|
||||
final CompoundBinaryTag data = BlockUtils.extractClientNbt(block);
|
||||
chunk.sendPacketToViewers(new BlockEntityDataPacket(blockPosition, registry.blockEntityId(), data));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package net.minestom.server.instance;
|
||||
|
||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||
import net.kyori.adventure.nbt.LongArrayBinaryTag;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.ServerFlag;
|
||||
import net.minestom.server.collision.Shape;
|
||||
|
@ -16,8 +18,6 @@ import net.minestom.server.utils.NamespaceID;
|
|||
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBT;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
@ -202,14 +202,17 @@ public class LightingChunk extends DynamicChunk {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected NBTCompound computeHeightmap() {
|
||||
protected CompoundBinaryTag computeHeightmap() {
|
||||
// Heightmap
|
||||
int[] heightmap = getHeightmap();
|
||||
int dimensionHeight = getInstance().getDimensionType().getHeight();
|
||||
final int bitsForHeight = MathUtils.bitsToRepresent(dimensionHeight);
|
||||
return NBT.Compound(Map.of(
|
||||
"MOTION_BLOCKING", NBT.LongArray(encodeBlocks(heightmap, bitsForHeight)),
|
||||
"WORLD_SURFACE", NBT.LongArray(encodeBlocks(heightmap, bitsForHeight))));
|
||||
|
||||
LongArrayBinaryTag encoded = LongArrayBinaryTag.longArrayBinaryTag(encodeBlocks(heightmap, bitsForHeight));
|
||||
return CompoundBinaryTag.builder()
|
||||
.put("MOTION_BLOCKING", encoded)
|
||||
.put("WORLD_SURFACE", encoded)
|
||||
.build();
|
||||
}
|
||||
|
||||
// Lazy compute heightmap
|
||||
|
|
|
@ -0,0 +1,515 @@
|
|||
package net.minestom.server.instance.anvil;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntIntImmutablePair;
|
||||
import net.kyori.adventure.nbt.*;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.instance.Chunk;
|
||||
import net.minestom.server.instance.IChunkLoader;
|
||||
import net.minestom.server.instance.Instance;
|
||||
import net.minestom.server.instance.Section;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.instance.block.BlockHandler;
|
||||
import net.minestom.server.utils.ArrayUtils;
|
||||
import net.minestom.server.utils.NamespaceID;
|
||||
import net.minestom.server.utils.async.AsyncUtils;
|
||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
import net.minestom.server.world.biomes.Biome;
|
||||
import net.minestom.server.world.biomes.BiomeManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
public class AnvilLoader implements IChunkLoader {
|
||||
private final static Logger LOGGER = LoggerFactory.getLogger(AnvilLoader.class);
|
||||
private static final BiomeManager BIOME_MANAGER = MinecraftServer.getBiomeManager();
|
||||
private final static Biome PLAINS = BIOME_MANAGER.getByName(NamespaceID.from("minecraft:plains"));
|
||||
|
||||
private final Map<String, RegionFile> alreadyLoaded = new ConcurrentHashMap<>();
|
||||
private final Path path;
|
||||
private final Path levelPath;
|
||||
private final Path regionPath;
|
||||
|
||||
private static class RegionCache extends ConcurrentHashMap<IntIntImmutablePair, Set<IntIntImmutablePair>> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the chunks currently loaded per region. Used to determine when a region file can be unloaded.
|
||||
*/
|
||||
private final RegionCache perRegionLoadedChunks = new RegionCache();
|
||||
private final ReentrantLock perRegionLoadedChunksLock = new ReentrantLock();
|
||||
|
||||
// thread local to avoid contention issues with locks
|
||||
// private final ThreadLocal<Int2ObjectMap<BlockState>> blockStateId2ObjectCacheTLS = ThreadLocal.withInitial(Int2ObjectArrayMap::new);
|
||||
|
||||
public AnvilLoader(@NotNull Path path) {
|
||||
this.path = path;
|
||||
this.levelPath = path.resolve("level.dat");
|
||||
this.regionPath = path.resolve("region");
|
||||
}
|
||||
|
||||
public AnvilLoader(@NotNull String path) {
|
||||
this(Path.of(path));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadInstance(@NotNull Instance instance) {
|
||||
if (!Files.exists(levelPath)) {
|
||||
return;
|
||||
}
|
||||
try (InputStream is = Files.newInputStream(levelPath)) {
|
||||
final CompoundBinaryTag tag = BinaryTagIO.reader().readNamed(is, BinaryTagIO.Compression.GZIP).getValue();
|
||||
Files.copy(levelPath, path.resolve("level.dat_old"), StandardCopyOption.REPLACE_EXISTING);
|
||||
instance.tagHandler().updateContent(tag);
|
||||
} catch (IOException e) {
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull CompletableFuture<@Nullable Chunk> loadChunk(@NotNull Instance instance, int chunkX, int chunkZ) {
|
||||
if (!Files.exists(path)) {
|
||||
// No world folder
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
try {
|
||||
return loadMCA(instance, chunkX, chunkZ);
|
||||
} catch (Exception e) {
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
}
|
||||
|
||||
private @NotNull CompletableFuture<@Nullable Chunk> loadMCA(Instance instance, int chunkX, int chunkZ) throws IOException {
|
||||
final RegionFile mcaFile = getMCAFile(chunkX, chunkZ);
|
||||
if (mcaFile == null)
|
||||
return CompletableFuture.completedFuture(null);
|
||||
final CompoundBinaryTag chunkData = mcaFile.readChunkData(chunkX, chunkZ);
|
||||
if (chunkData == null)
|
||||
return CompletableFuture.completedFuture(null);
|
||||
|
||||
// Load the chunk data (assuming it is fully generated)
|
||||
final Chunk chunk = instance.getChunkSupplier().createChunk(instance, chunkX, chunkZ);
|
||||
synchronized (chunk) { // todo: boo, synchronized
|
||||
final String status = chunkData.getString("status");
|
||||
|
||||
// TODO: Should we handle other statuses?
|
||||
if (status.isEmpty() || "minecraft:full".equals(status)) {
|
||||
// TODO: Parallelize block, block entities and biome loading
|
||||
// Blocks + Biomes
|
||||
loadSections(chunk, chunkData);
|
||||
|
||||
// Block entities
|
||||
loadBlockEntities(chunk, chunkData);
|
||||
} else {
|
||||
LOGGER.warn("Skipping partially generated chunk at {}, {} with status {}", chunkX, chunkZ, status);
|
||||
}
|
||||
}
|
||||
|
||||
// Cache the index of the loaded chunk
|
||||
perRegionLoadedChunksLock.lock();
|
||||
try {
|
||||
int regionX = ChunkUtils.toRegionCoordinate(chunkX);
|
||||
int regionZ = ChunkUtils.toRegionCoordinate(chunkZ);
|
||||
var chunks = perRegionLoadedChunks.computeIfAbsent(new IntIntImmutablePair(regionX, regionZ), r -> new HashSet<>()); // region cache may have been removed on another thread due to unloadChunk
|
||||
chunks.add(new IntIntImmutablePair(chunkX, chunkZ));
|
||||
} finally {
|
||||
perRegionLoadedChunksLock.unlock();
|
||||
}
|
||||
return CompletableFuture.completedFuture(chunk);
|
||||
}
|
||||
|
||||
private @Nullable RegionFile getMCAFile(int chunkX, int chunkZ) {
|
||||
final int regionX = ChunkUtils.toRegionCoordinate(chunkX);
|
||||
final int regionZ = ChunkUtils.toRegionCoordinate(chunkZ);
|
||||
return alreadyLoaded.computeIfAbsent(RegionFile.getFileName(regionX, regionZ), n -> {
|
||||
final Path regionPath = this.regionPath.resolve(n);
|
||||
if (!Files.exists(regionPath)) {
|
||||
return null;
|
||||
}
|
||||
perRegionLoadedChunksLock.lock();
|
||||
try {
|
||||
Set<IntIntImmutablePair> previousVersion = perRegionLoadedChunks.put(new IntIntImmutablePair(regionX, regionZ), new HashSet<>());
|
||||
assert previousVersion == null : "The AnvilLoader cache should not already have data for this region.";
|
||||
return new RegionFile(regionPath);
|
||||
} catch (IOException e) {
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
return null;
|
||||
} finally {
|
||||
perRegionLoadedChunksLock.unlock();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadSections(@NotNull Chunk chunk, @NotNull CompoundBinaryTag chunkData) {
|
||||
for (BinaryTag sectionTag : chunkData.getList("sections", BinaryTagTypes.COMPOUND)) {
|
||||
final CompoundBinaryTag sectionData = (CompoundBinaryTag) sectionTag;
|
||||
|
||||
final int sectionY = sectionData.getInt("Y", Integer.MIN_VALUE);
|
||||
Check.stateCondition(sectionY == Integer.MIN_VALUE, "Missing section Y value");
|
||||
final int yOffset = Chunk.CHUNK_SECTION_SIZE * sectionY;
|
||||
|
||||
if (sectionY < chunk.getMinSection() || sectionY >= chunk.getMaxSection()) {
|
||||
// Vanilla stores a section below and above the world for lighting, throw it out.
|
||||
continue;
|
||||
}
|
||||
|
||||
final Section section = chunk.getSection(sectionY);
|
||||
|
||||
// Lighting
|
||||
if (sectionData.get("SkyLight") instanceof ByteArrayBinaryTag skyLightTag && skyLightTag.size() == 2048) {
|
||||
section.setSkyLight(skyLightTag.value());
|
||||
}
|
||||
if (sectionData.get("BlockLight") instanceof ByteArrayBinaryTag blockLightTag && blockLightTag.size() == 2048) {
|
||||
section.setBlockLight(blockLightTag.value());
|
||||
}
|
||||
|
||||
{ // Biomes
|
||||
final CompoundBinaryTag biomesTag = sectionData.getCompound("biomes");
|
||||
final ListBinaryTag biomePaletteTag = biomesTag.getList("palette", BinaryTagTypes.STRING);
|
||||
Biome[] convertedPalette = loadBiomePalette(biomePaletteTag);
|
||||
|
||||
if (convertedPalette.length == 1) {
|
||||
// One solid block, no need to check the data
|
||||
section.biomePalette().fill(BIOME_MANAGER.getId(convertedPalette[0]));
|
||||
} else if (convertedPalette.length > 1) {
|
||||
final long[] packedIndices = biomesTag.getLongArray("data");
|
||||
Check.stateCondition(packedIndices.length == 0, "Missing packed biomes data");
|
||||
int[] biomeIndices = new int[64];
|
||||
ArrayUtils.unpack(biomeIndices, packedIndices, packedIndices.length * 64 / biomeIndices.length);
|
||||
|
||||
section.biomePalette().setAll((x, y, z) -> {
|
||||
final int index = x + z * 4 + y * 16;
|
||||
final Biome biome = convertedPalette[biomeIndices[index]];
|
||||
return BIOME_MANAGER.getId(biome);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
{ // Blocks
|
||||
final CompoundBinaryTag blockStatesTag = sectionData.getCompound("block_states");
|
||||
final ListBinaryTag blockPaletteTag = blockStatesTag.getList("palette", BinaryTagTypes.COMPOUND);
|
||||
Block[] convertedPalette = loadBlockPalette(blockPaletteTag);
|
||||
if (blockPaletteTag.size() == 1) {
|
||||
// One solid block, no need to check the data
|
||||
section.blockPalette().fill(convertedPalette[0].stateId());
|
||||
} else if (blockPaletteTag.size() > 1) {
|
||||
final long[] packedStates = blockStatesTag.getLongArray("data");
|
||||
Check.stateCondition(packedStates.length == 0, "Missing packed states data");
|
||||
int[] blockStateIndices = new int[Chunk.CHUNK_SECTION_SIZE * Chunk.CHUNK_SECTION_SIZE * Chunk.CHUNK_SECTION_SIZE];
|
||||
ArrayUtils.unpack(blockStateIndices, packedStates, packedStates.length * 64 / blockStateIndices.length);
|
||||
|
||||
for (int y = 0; y < Chunk.CHUNK_SECTION_SIZE; y++) {
|
||||
for (int z = 0; z < Chunk.CHUNK_SECTION_SIZE; z++) {
|
||||
for (int x = 0; x < Chunk.CHUNK_SECTION_SIZE; x++) {
|
||||
try {
|
||||
final int blockIndex = y * Chunk.CHUNK_SECTION_SIZE * Chunk.CHUNK_SECTION_SIZE + z * Chunk.CHUNK_SECTION_SIZE + x;
|
||||
final int paletteIndex = blockStateIndices[blockIndex];
|
||||
final Block block = convertedPalette[paletteIndex];
|
||||
|
||||
chunk.setBlock(x, y + yOffset, z, block);
|
||||
} catch (Exception e) {
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Block[] loadBlockPalette(@NotNull ListBinaryTag paletteTag) {
|
||||
Block[] convertedPalette = new Block[paletteTag.size()];
|
||||
for (int i = 0; i < convertedPalette.length; i++) {
|
||||
CompoundBinaryTag paletteEntry = paletteTag.getCompound(i);
|
||||
String blockName = paletteEntry.getString("Name");
|
||||
if (blockName.equals("minecraft:air")) {
|
||||
convertedPalette[i] = Block.AIR;
|
||||
} else {
|
||||
Block block = Objects.requireNonNull(Block.fromNamespaceId(blockName), "Unknown block " + blockName);
|
||||
// Properties
|
||||
final Map<String, String> properties = new HashMap<>();
|
||||
CompoundBinaryTag propertiesNBT = paletteEntry.getCompound("Properties");
|
||||
for (var property : propertiesNBT) {
|
||||
if (property.getValue() instanceof StringBinaryTag propertyValue) {
|
||||
properties.put(property.getKey(), propertyValue.value());
|
||||
} else {
|
||||
LOGGER.warn("Fail to parse block state properties {}, expected a string for {}, but contents were {}",
|
||||
propertiesNBT, property.getKey(), TagStringIOExt.writeTag(property.getValue()));
|
||||
}
|
||||
}
|
||||
if (!properties.isEmpty()) block = block.withProperties(properties);
|
||||
|
||||
// Handler
|
||||
final BlockHandler handler = MinecraftServer.getBlockManager().getHandler(block.name());
|
||||
if (handler != null) block = block.withHandler(handler);
|
||||
|
||||
convertedPalette[i] = block;
|
||||
}
|
||||
}
|
||||
return convertedPalette;
|
||||
}
|
||||
|
||||
private Biome[] loadBiomePalette(@NotNull ListBinaryTag paletteTag) {
|
||||
Biome[] convertedPalette = new Biome[paletteTag.size()];
|
||||
for (int i = 0; i < convertedPalette.length; i++) {
|
||||
final String name = paletteTag.getString(i);
|
||||
convertedPalette[i] = Objects.requireNonNullElse(BIOME_MANAGER.getByName(name), PLAINS);
|
||||
}
|
||||
return convertedPalette;
|
||||
}
|
||||
|
||||
private void loadBlockEntities(@NotNull Chunk loadedChunk, @NotNull CompoundBinaryTag chunkData) {
|
||||
for (BinaryTag blockEntityTag : chunkData.getList("block_entities", BinaryTagTypes.COMPOUND)) {
|
||||
final CompoundBinaryTag blockEntity = (CompoundBinaryTag) blockEntityTag;
|
||||
|
||||
final int x = blockEntity.getInt("x");
|
||||
final int y = blockEntity.getInt("y");
|
||||
final int z = blockEntity.getInt("z");
|
||||
Block block = loadedChunk.getBlock(x, y, z);
|
||||
|
||||
// Load the block handler if the id is present
|
||||
if (blockEntity.get("id") instanceof StringBinaryTag blockEntityId) {
|
||||
final BlockHandler handler = MinecraftServer.getBlockManager().getHandlerOrDummy(blockEntityId.value());
|
||||
block = block.withHandler(handler);
|
||||
}
|
||||
|
||||
// Remove anvil tags
|
||||
CompoundBinaryTag trimmedTag = CompoundBinaryTag.builder().put(blockEntity)
|
||||
.remove("id").remove("keepPacked")
|
||||
.remove("x").remove("y").remove("z")
|
||||
.build();
|
||||
|
||||
// Place block
|
||||
final var finalBlock = trimmedTag.size() > 0 ? block.withNbt(trimmedTag) : block;
|
||||
loadedChunk.setBlock(x, y, z, finalBlock);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull CompletableFuture<Void> saveInstance(@NotNull Instance instance) {
|
||||
final CompoundBinaryTag nbt = instance.tagHandler().asCompound();
|
||||
if (nbt.size() == 0) {
|
||||
// Instance has no data
|
||||
return AsyncUtils.VOID_FUTURE;
|
||||
}
|
||||
try (OutputStream os = Files.newOutputStream(levelPath, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
|
||||
BinaryTagIO.writer().writeNamed(Map.entry("", nbt), os, BinaryTagIO.Compression.GZIP);
|
||||
} catch (IOException e) {
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
}
|
||||
return AsyncUtils.VOID_FUTURE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull CompletableFuture<Void> saveChunk(@NotNull Chunk chunk) {
|
||||
final int chunkX = chunk.getChunkX();
|
||||
final int chunkZ = chunk.getChunkZ();
|
||||
|
||||
// Find the region file or create an empty one if missing
|
||||
RegionFile mcaFile = getMCAFile(chunkX, chunkZ);
|
||||
if (mcaFile == null) {
|
||||
final int regionX = ChunkUtils.toRegionCoordinate(chunkX);
|
||||
final int regionZ = ChunkUtils.toRegionCoordinate(chunkZ);
|
||||
final String regionFileName = RegionFile.getFileName(regionX, regionZ);
|
||||
try {
|
||||
Path regionFile = regionPath.resolve(regionFileName);
|
||||
if (!Files.exists(regionFile)) {
|
||||
Files.createDirectories(regionFile.getParent());
|
||||
Files.createFile(regionFile);
|
||||
}
|
||||
|
||||
mcaFile = new RegionFile(regionFile);
|
||||
alreadyLoaded.put(regionFileName, mcaFile);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Failed to create region file for " + chunkX + ", " + chunkZ, e);
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
return AsyncUtils.VOID_FUTURE;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
final CompoundBinaryTag.Builder chunkData = CompoundBinaryTag.builder();
|
||||
|
||||
chunkData.putInt("DataVersion", MinecraftServer.DATA_VERSION);
|
||||
chunkData.putInt("xPos", chunkX);
|
||||
chunkData.putInt("zPos", chunkZ);
|
||||
chunkData.putInt("yPos", chunk.getMinSection());
|
||||
chunkData.putString("status", "minecraft:full");
|
||||
chunkData.putLong("LastUpdate", chunk.getInstance().getWorldAge());
|
||||
|
||||
saveSectionData(chunk, chunkData);
|
||||
|
||||
LOGGER.debug("Attempt saving at {} {}", chunk.getChunkX(), chunk.getChunkZ());
|
||||
mcaFile.writeChunkData(chunkX, chunkZ, chunkData.build());
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Failed to save chunk " + chunkX + ", " + chunkZ, e);
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
}
|
||||
return AsyncUtils.VOID_FUTURE;
|
||||
}
|
||||
|
||||
// private BlockState getBlockState(final Block block) {
|
||||
// return blockStateId2ObjectCacheTLS.get().computeIfAbsent(block.stateId(), _unused -> new BlockState(block.name(), block.properties()));
|
||||
// }
|
||||
|
||||
private void saveSectionData(@NotNull Chunk chunk, @NotNull CompoundBinaryTag.Builder chunkData) {
|
||||
|
||||
|
||||
final int minY = chunk.getMinSection() * Chunk.CHUNK_SECTION_SIZE;
|
||||
final int maxY = chunk.getMaxSection() * Chunk.CHUNK_SECTION_SIZE - 1;
|
||||
for (int sectionY = chunk.getMinSection(); sectionY < chunk.getMaxSection(); sectionY++) {
|
||||
final Section section = chunk.getSection(sectionY);
|
||||
|
||||
final CompoundBinaryTag.Builder sectionData = CompoundBinaryTag.builder();
|
||||
sectionData.putInt("Y", sectionY);
|
||||
|
||||
// Lighting
|
||||
byte[] skyLight = section.skyLight().array();
|
||||
if (skyLight != null && skyLight.length > 0)
|
||||
sectionData.putByteArray("SkyLight", skyLight);
|
||||
byte[] blockLight = section.blockLight().array();
|
||||
if (blockLight != null && blockLight.length > 0)
|
||||
sectionData.putByteArray("BlockLight", blockLight);
|
||||
|
||||
// Build block & biome palettes
|
||||
//todo
|
||||
// int[] blockStates = new int[Chunk.CHUNK_SECTION_SIZE * Chunk.CHUNK_SECTION_SIZE * Chunk.CHUNK_SECTION_SIZE];
|
||||
// int[] biomes = new int[64];
|
||||
//
|
||||
// for (int localY = 0; localY < Chunk.CHUNK_SECTION_SIZE; localY++) {
|
||||
// for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z++) {
|
||||
// for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) {
|
||||
//
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
throw new UnsupportedOperationException("Not implemented");
|
||||
}
|
||||
|
||||
}
|
||||
// private void save(Chunk chunk, ChunkWriter chunkWriter) {
|
||||
// final int minY = chunk.getMinSection() * Chunk.CHUNK_SECTION_SIZE;
|
||||
// final int maxY = chunk.getMaxSection() * Chunk.CHUNK_SECTION_SIZE - 1;
|
||||
// chunkWriter.setYPos(minY);
|
||||
// List<NBTCompound> blockEntities = new ArrayList<>();
|
||||
// chunkWriter.setStatus(ChunkColumn.GenerationStatus.Full);
|
||||
//
|
||||
// List<NBTCompound> sectionData = new ArrayList<>((maxY - minY + 1) / Chunk.CHUNK_SECTION_SIZE);
|
||||
// int[] palettedBiomes = new int[ChunkSection.Companion.getBiomeArraySize()];
|
||||
// int[] palettedBlockStates = new int[Chunk.CHUNK_SIZE_X * Chunk.CHUNK_SECTION_SIZE * Chunk.CHUNK_SIZE_Z];
|
||||
// for (int sectionY = chunk.getMinSection(); sectionY < chunk.getMaxSection(); sectionY++) {
|
||||
|
||||
// for (int sectionLocalY = 0; sectionLocalY < Chunk.CHUNK_SECTION_SIZE; sectionLocalY++) {
|
||||
// for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z++) {
|
||||
// for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) {
|
||||
// final int y = sectionLocalY + sectionY * Chunk.CHUNK_SECTION_SIZE;
|
||||
//
|
||||
// final int blockIndex = x + sectionLocalY * 16 * 16 + z * 16;
|
||||
//
|
||||
// final Block block = chunk.getBlock(x, y, z);
|
||||
//
|
||||
// final BlockState hephaistosBlockState = getBlockState(block);
|
||||
// blockPalette.increaseReference(hephaistosBlockState);
|
||||
//
|
||||
// palettedBlockStates[blockIndex] = blockPalette.getPaletteIndex(hephaistosBlockState);
|
||||
//
|
||||
// // biome are stored for 4x4x4 volumes, avoid unnecessary work
|
||||
// if (x % 4 == 0 && sectionLocalY % 4 == 0 && z % 4 == 0) {
|
||||
// int biomeIndex = (x / 4) + (sectionLocalY / 4) * 4 * 4 + (z / 4) * 4;
|
||||
// final Biome biome = chunk.getBiome(x, y, z);
|
||||
// final String biomeName = biome.name();
|
||||
//
|
||||
// biomePalette.increaseReference(biomeName);
|
||||
// palettedBiomes[biomeIndex] = biomePalette.getPaletteIndex(biomeName);
|
||||
// }
|
||||
//
|
||||
// // Block entities
|
||||
// final BlockHandler handler = block.handler();
|
||||
// final NBTCompound originalNBT = block.nbt();
|
||||
// if (originalNBT != null || handler != null) {
|
||||
// MutableNBTCompound nbt = originalNBT != null ?
|
||||
// originalNBT.toMutableCompound() : new MutableNBTCompound();
|
||||
//
|
||||
// if (handler != null) {
|
||||
// nbt.setString("id", handler.getNamespaceId().asString());
|
||||
// }
|
||||
// nbt.setInt("x", x + Chunk.CHUNK_SIZE_X * chunk.getChunkX());
|
||||
// nbt.setInt("y", y);
|
||||
// nbt.setInt("z", z + Chunk.CHUNK_SIZE_Z * chunk.getChunkZ());
|
||||
// nbt.setByte("keepPacked", (byte) 0);
|
||||
// blockEntities.add(nbt.toCompound());
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// sectionWriter.setPalettedBiomes(biomePalette, palettedBiomes);
|
||||
// sectionWriter.setPalettedBlockStates(blockPalette, palettedBlockStates);
|
||||
//
|
||||
// sectionData.add(sectionWriter.toNBT());
|
||||
// }
|
||||
//
|
||||
// chunkWriter.setSectionsData(NBT.List(NBTType.TAG_Compound, sectionData));
|
||||
// chunkWriter.setBlockEntityData(NBT.List(NBTType.TAG_Compound, blockEntities));
|
||||
// }
|
||||
|
||||
/**
|
||||
* Unload a given chunk. Also unloads a region when no chunk from that region is loaded.
|
||||
*
|
||||
* @param chunk the chunk to unload
|
||||
*/
|
||||
@Override
|
||||
public void unloadChunk(Chunk chunk) {
|
||||
final int regionX = ChunkUtils.toRegionCoordinate(chunk.getChunkX());
|
||||
final int regionZ = ChunkUtils.toRegionCoordinate(chunk.getChunkZ());
|
||||
final IntIntImmutablePair regionKey = new IntIntImmutablePair(regionX, regionZ);
|
||||
|
||||
perRegionLoadedChunksLock.lock();
|
||||
try {
|
||||
Set<IntIntImmutablePair> chunks = perRegionLoadedChunks.get(regionKey);
|
||||
if (chunks != null) { // if null, trying to unload a chunk from a region that was not created by the AnvilLoader
|
||||
// don't check return value, trying to unload a chunk not created by the AnvilLoader is valid
|
||||
chunks.remove(new IntIntImmutablePair(chunk.getChunkX(), chunk.getChunkZ()));
|
||||
|
||||
if (chunks.isEmpty()) {
|
||||
perRegionLoadedChunks.remove(regionKey);
|
||||
RegionFile regionFile = alreadyLoaded.remove(RegionFile.getFileName(regionX, regionZ));
|
||||
if (regionFile != null) {
|
||||
try {
|
||||
regionFile.close();
|
||||
} catch (IOException e) {
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
perRegionLoadedChunksLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsParallelLoading() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsParallelSaving() {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,214 @@
|
|||
package net.minestom.server.instance.anvil;
|
||||
|
||||
import it.unimi.dsi.fastutil.booleans.BooleanArrayList;
|
||||
import it.unimi.dsi.fastutil.booleans.BooleanList;
|
||||
import net.kyori.adventure.nbt.BinaryTagIO;
|
||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* Implements a thread-safe reader and writer for Minecraft region files.
|
||||
*
|
||||
* @see <a href="https://minecraft.wiki/w/Region_file_format">Region file format</a>
|
||||
* @see <a href="https://github.com/Minestom/Hephaistos/blob/master/common/src/main/kotlin/org/jglrxavpok/hephaistos/mca/RegionFile.kt">Hephaistos implementation</a>
|
||||
*/
|
||||
final class RegionFile implements AutoCloseable {
|
||||
|
||||
private static final int MAX_ENTRY_COUNT = 1024;
|
||||
private static final int SECTOR_SIZE = 4096;
|
||||
private static final int SECTOR_1MB = 1024 * 1024 / SECTOR_SIZE;
|
||||
private static final int HEADER_LENGTH = MAX_ENTRY_COUNT * 2 * 4; // 2 4-byte fields per entry
|
||||
private static final int CHUNK_HEADER_LENGTH = 4 + 1; // Length + Compression type (todo non constant to support custom compression)
|
||||
|
||||
private static final int COMPRESSION_ZLIB = 2;
|
||||
|
||||
private static final BinaryTagIO.Reader TAG_READER = BinaryTagIO.unlimitedReader();
|
||||
private static final BinaryTagIO.Writer TAG_WRITER = BinaryTagIO.writer();
|
||||
|
||||
public static @NotNull String getFileName(int regionX, int regionZ) {
|
||||
return "r." + regionX + "." + regionZ + ".mca";
|
||||
}
|
||||
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
private final RandomAccessFile file;
|
||||
|
||||
private final int[] locations = new int[MAX_ENTRY_COUNT];
|
||||
private final int[] timestamps = new int[MAX_ENTRY_COUNT];
|
||||
private final BooleanList freeSectors = new BooleanArrayList(2);
|
||||
|
||||
public RegionFile(@NotNull Path path) throws IOException {
|
||||
this.file = new RandomAccessFile(path.toFile(), "rw");
|
||||
|
||||
readHeader();
|
||||
}
|
||||
|
||||
public boolean hasChunkData(int chunkX, int chunkZ) {
|
||||
lock.lock();
|
||||
try {
|
||||
return locations[getChunkIndex(chunkX, chunkZ)] != 0;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable CompoundBinaryTag readChunkData(int chunkX, int chunkZ) throws IOException {
|
||||
lock.lock();
|
||||
try {
|
||||
if (!hasChunkData(chunkX, chunkZ)) return null;
|
||||
|
||||
int location = locations[getChunkIndex(chunkX, chunkZ)];
|
||||
file.seek((long) (location >> 8) * SECTOR_SIZE); // Move to start of first sector
|
||||
int length = file.readInt();
|
||||
int compressionType = file.readByte();
|
||||
BinaryTagIO.Compression compression = switch (compressionType) {
|
||||
case 1 -> BinaryTagIO.Compression.GZIP;
|
||||
case COMPRESSION_ZLIB -> BinaryTagIO.Compression.ZLIB;
|
||||
case 3 -> BinaryTagIO.Compression.NONE;
|
||||
default -> throw new IOException("Unsupported compression type: " + compressionType);
|
||||
};
|
||||
|
||||
// Read the raw content
|
||||
byte[] data = new byte[length - 1];
|
||||
file.read(data);
|
||||
|
||||
// Parse it as a compound tag
|
||||
return TAG_READER.read(new ByteArrayInputStream(data), compression);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void writeChunkData(int chunkX, int chunkZ, @NotNull CompoundBinaryTag data) throws IOException {
|
||||
// Write the data (compressed)
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
TAG_WRITER.writeNamed(Map.entry("", data), out, BinaryTagIO.Compression.ZLIB);
|
||||
byte[] dataBytes = out.toByteArray();
|
||||
int chunkLength = CHUNK_HEADER_LENGTH + dataBytes.length;
|
||||
|
||||
int sectorCount = (int) Math.ceil(chunkLength / (double) SECTOR_SIZE);
|
||||
Check.stateCondition(sectorCount >= SECTOR_1MB, "Chunk data is too large to fit in a region file");
|
||||
|
||||
lock.lock();
|
||||
try {
|
||||
// We don't attempt to reuse the current allocation, just write it to a new position and free the old one.
|
||||
int oldLocation = getChunkIndex(chunkX, chunkZ);
|
||||
|
||||
// Find a new location
|
||||
int firstSector = findFreeSectors(sectorCount);
|
||||
if (firstSector == -1) {
|
||||
firstSector = allocSectors(sectorCount);
|
||||
}
|
||||
int newLocation = (firstSector << 8) | sectorCount;
|
||||
|
||||
// Mark the sectors as used & free the old sectors
|
||||
markLocation(oldLocation, true);
|
||||
markLocation(newLocation, false);
|
||||
|
||||
// Write the chunk data
|
||||
file.seek((long) firstSector * SECTOR_SIZE);
|
||||
file.writeInt(chunkLength);
|
||||
file.writeByte(COMPRESSION_ZLIB);
|
||||
file.write(dataBytes);
|
||||
|
||||
// Update the header and write it
|
||||
locations[oldLocation] = newLocation;
|
||||
timestamps[oldLocation] = (int) (System.currentTimeMillis() / 1000);
|
||||
writeHeader();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
file.close();
|
||||
}
|
||||
|
||||
private int getChunkIndex(int chunkX, int chunkZ) {
|
||||
return (ChunkUtils.toRegionLocal(chunkZ) << 5) | ChunkUtils.toRegionLocal(chunkX);
|
||||
}
|
||||
|
||||
private void readHeader() throws IOException {
|
||||
file.seek(0);
|
||||
if (file.length() < HEADER_LENGTH) {
|
||||
// new file, fill in data
|
||||
file.write(new byte[HEADER_LENGTH]);
|
||||
}
|
||||
|
||||
//todo: addPadding()
|
||||
|
||||
final long totalSectors = file.length() / SECTOR_SIZE;
|
||||
for (int i = 0; i < totalSectors; i++) freeSectors.add(true);
|
||||
|
||||
// Read locations
|
||||
file.seek(0);
|
||||
for (int i = 0; i < MAX_ENTRY_COUNT; i++) {
|
||||
int location = locations[i] = file.readInt();
|
||||
if (location != 0) {
|
||||
markLocation(location, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Read timestamps
|
||||
for (int i = 0; i < MAX_ENTRY_COUNT; i++) {
|
||||
timestamps[i] = file.readInt();
|
||||
}
|
||||
}
|
||||
|
||||
private void writeHeader() throws IOException {
|
||||
file.seek(0);
|
||||
for (int location : locations) {
|
||||
file.writeInt(location);
|
||||
}
|
||||
for (int timestamp : timestamps) {
|
||||
file.writeInt(timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
private int findFreeSectors(int length) {
|
||||
for (int start = 0; start < freeSectors.size() - length; start++) {
|
||||
boolean found = true;
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (!freeSectors.getBoolean(start++)) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) return start;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private int allocSectors(int count) throws IOException {
|
||||
var eof = file.length();
|
||||
file.seek(eof);
|
||||
|
||||
byte[] emptySector = new byte[SECTOR_SIZE];
|
||||
for (int i = 0; i < count; i++) {
|
||||
freeSectors.add(true);
|
||||
file.write(emptySector);
|
||||
}
|
||||
|
||||
return (int) (eof / SECTOR_SIZE);
|
||||
}
|
||||
|
||||
private void markLocation(int location, boolean free) {
|
||||
int sectorCount = location & 0xFF;
|
||||
int sectorStart = location >> 8;
|
||||
Check.stateCondition(sectorStart + sectorCount > freeSectors.size(), "Invalid sector count");
|
||||
for (int i = sectorStart; i < sectorStart + sectorCount; i++) {
|
||||
freeSectors.set(i, free);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,15 +1,15 @@
|
|||
package net.minestom.server.instance.block;
|
||||
|
||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||
import net.minestom.server.coordinate.Point;
|
||||
import net.minestom.server.instance.Instance;
|
||||
import net.minestom.server.instance.batch.Batch;
|
||||
import net.minestom.server.registry.StaticProtocolObject;
|
||||
import net.minestom.server.registry.Registry;
|
||||
import net.minestom.server.registry.StaticProtocolObject;
|
||||
import net.minestom.server.tag.Tag;
|
||||
import net.minestom.server.tag.TagReadable;
|
||||
import net.minestom.server.utils.NamespaceID;
|
||||
import org.jetbrains.annotations.*;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
@ -67,7 +67,7 @@ public sealed interface Block extends StaticProtocolObject, TagReadable, Blocks
|
|||
* @return a new block with different nbt
|
||||
*/
|
||||
@Contract(pure = true)
|
||||
@NotNull Block withNbt(@Nullable NBTCompound compound);
|
||||
@NotNull Block withNbt(@Nullable CompoundBinaryTag compound);
|
||||
|
||||
/**
|
||||
* Creates a new block with the specified {@link BlockHandler handler}.
|
||||
|
@ -86,7 +86,7 @@ public sealed interface Block extends StaticProtocolObject, TagReadable, Blocks
|
|||
* @return the block nbt, null if not present
|
||||
*/
|
||||
@Contract(pure = true)
|
||||
@Nullable NBTCompound nbt();
|
||||
@Nullable CompoundBinaryTag nbt();
|
||||
|
||||
@Contract(pure = true)
|
||||
default boolean hasNbt() {
|
||||
|
|
|
@ -4,6 +4,7 @@ import com.github.benmanes.caffeine.cache.Cache;
|
|||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMaps;
|
||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||
import net.minestom.server.registry.Registry;
|
||||
import net.minestom.server.tag.Tag;
|
||||
import net.minestom.server.utils.ArrayUtils;
|
||||
|
@ -14,8 +15,6 @@ import org.jetbrains.annotations.NotNull;
|
|||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.UnknownNullability;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
||||
import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
|
@ -23,7 +22,7 @@ import java.util.function.Function;
|
|||
|
||||
record BlockImpl(@NotNull Registry.BlockEntry registry,
|
||||
byte @NotNull [] propertiesArray,
|
||||
@Nullable NBTCompound nbt,
|
||||
@Nullable CompoundBinaryTag nbt,
|
||||
@Nullable BlockHandler handler) implements Block {
|
||||
// Block state -> block object
|
||||
private static final ObjectArray<Block> BLOCK_STATE_MAP = ObjectArray.singleThread();
|
||||
|
@ -86,7 +85,7 @@ record BlockImpl(@NotNull Registry.BlockEntry registry,
|
|||
final int defaultState = properties.getInt("defaultStateId");
|
||||
return getState(defaultState);
|
||||
});
|
||||
private static final Cache<NBTCompound, NBTCompound> NBT_CACHE = Caffeine.newBuilder()
|
||||
private static final Cache<CompoundBinaryTag, CompoundBinaryTag> NBT_CACHE = Caffeine.newBuilder()
|
||||
.expireAfterWrite(Duration.ofMinutes(5))
|
||||
.weakValues()
|
||||
.build();
|
||||
|
@ -144,14 +143,16 @@ record BlockImpl(@NotNull Registry.BlockEntry registry,
|
|||
|
||||
@Override
|
||||
public @NotNull <T> Block withTag(@NotNull Tag<T> tag, @Nullable T value) {
|
||||
var temporaryNbt = new MutableNBTCompound(Objects.requireNonNullElse(nbt, NBTCompound.EMPTY));
|
||||
tag.write(temporaryNbt, value);
|
||||
final var finalNbt = temporaryNbt.getSize() > 0 ? NBT_CACHE.get(temporaryNbt.toCompound(), Function.identity()) : null;
|
||||
var builder = CompoundBinaryTag.builder();
|
||||
if (nbt != null) builder.put(nbt);
|
||||
tag.write(builder, value);
|
||||
var temporaryNbt = builder.build();
|
||||
final var finalNbt = temporaryNbt.size() > 0 ? NBT_CACHE.get(temporaryNbt, Function.identity()) : null;
|
||||
return new BlockImpl(registry, propertiesArray, finalNbt, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Block withNbt(@Nullable NBTCompound compound) {
|
||||
public @NotNull Block withNbt(@Nullable CompoundBinaryTag compound) {
|
||||
return new BlockImpl(registry, propertiesArray, compound, handler);
|
||||
}
|
||||
|
||||
|
@ -183,7 +184,7 @@ record BlockImpl(@NotNull Registry.BlockEntry registry,
|
|||
|
||||
@Override
|
||||
public <T> @UnknownNullability T getTag(@NotNull Tag<T> tag) {
|
||||
return tag.read(Objects.requireNonNullElse(nbt, NBTCompound.EMPTY));
|
||||
return tag.read(Objects.requireNonNullElse(nbt, CompoundBinaryTag.empty()));
|
||||
}
|
||||
|
||||
private Map<PropertiesHolder, BlockImpl> possibleProperties() {
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
package net.minestom.server.instance.block.predicate;
|
||||
|
||||
import net.kyori.adventure.nbt.BinaryTag;
|
||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.network.NetworkBuffer;
|
||||
import net.minestom.server.utils.nbt.BinaryTagSerializer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public record BlockPredicate(
|
||||
@Nullable BlockTypeFilter blocks,
|
||||
@Nullable PropertiesPredicate state,
|
||||
@Nullable CompoundBinaryTag nbt
|
||||
) implements Predicate<Block> {
|
||||
/**
|
||||
* Matches all blocks.
|
||||
*/
|
||||
public static final BlockPredicate ALL = new BlockPredicate(null, null, null);
|
||||
|
||||
public static final NetworkBuffer.Type<BlockPredicate> NETWORK_TYPE = new NetworkBuffer.Type<>() {
|
||||
@Override
|
||||
public void write(@NotNull NetworkBuffer buffer, BlockPredicate value) {
|
||||
buffer.writeOptional(BlockTypeFilter.NETWORK_TYPE, value.blocks);
|
||||
buffer.writeOptional(PropertiesPredicate.NETWORK_TYPE, value.state);
|
||||
buffer.writeOptional(NetworkBuffer.NBT, value.nbt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockPredicate read(@NotNull NetworkBuffer buffer) {
|
||||
return new BlockPredicate(
|
||||
buffer.readOptional(BlockTypeFilter.NETWORK_TYPE),
|
||||
buffer.readOptional(PropertiesPredicate.NETWORK_TYPE),
|
||||
(CompoundBinaryTag) buffer.readOptional(NetworkBuffer.NBT)
|
||||
);
|
||||
}
|
||||
};
|
||||
public static final BinaryTagSerializer<BlockPredicate> NBT_TYPE = new BinaryTagSerializer<>() {
|
||||
@Override
|
||||
public @NotNull BinaryTag write(@NotNull BlockPredicate value) {
|
||||
CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder();
|
||||
if (value.blocks != null)
|
||||
builder.put("blocks", BlockTypeFilter.NBT_TYPE.write(value.blocks));
|
||||
if (value.state != null)
|
||||
builder.put("state", PropertiesPredicate.NBT_TYPE.write(value.state));
|
||||
if (value.nbt != null)
|
||||
builder.put("nbt", value.nbt);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BlockPredicate read(@NotNull BinaryTag tag) {
|
||||
if (!(tag instanceof CompoundBinaryTag compound)) return BlockPredicate.ALL;
|
||||
|
||||
BinaryTag entry;
|
||||
BlockTypeFilter blocks = null;
|
||||
if ((entry = compound.get("blocks")) != null)
|
||||
blocks = BlockTypeFilter.NBT_TYPE.read(entry);
|
||||
PropertiesPredicate state = null;
|
||||
if ((entry = compound.get("state")) != null)
|
||||
state = PropertiesPredicate.NBT_TYPE.read(entry);
|
||||
CompoundBinaryTag nbt = null;
|
||||
if ((entry = compound.get("nbt")) != null)
|
||||
nbt = BinaryTagSerializer.COMPOUND_COERCED.read(entry);
|
||||
return new BlockPredicate(blocks, state, nbt);
|
||||
}
|
||||
};
|
||||
|
||||
public BlockPredicate(@NotNull BlockTypeFilter blocks) {
|
||||
this(blocks, null, null);
|
||||
}
|
||||
|
||||
public BlockPredicate(@NotNull PropertiesPredicate state) {
|
||||
this(null, state, null);
|
||||
}
|
||||
|
||||
public BlockPredicate(@NotNull CompoundBinaryTag nbt) {
|
||||
this(null, null, nbt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(@NotNull Block block) {
|
||||
throw new UnsupportedOperationException("not implemented");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
package net.minestom.server.instance.block.predicate;
|
||||
|
||||
import net.kyori.adventure.nbt.BinaryTag;
|
||||
import net.kyori.adventure.nbt.BinaryTagTypes;
|
||||
import net.kyori.adventure.nbt.ListBinaryTag;
|
||||
import net.kyori.adventure.nbt.StringBinaryTag;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.gamedata.tags.TagManager;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.network.NetworkBuffer;
|
||||
import net.minestom.server.utils.nbt.BinaryTagSerializer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public sealed interface BlockTypeFilter extends Predicate<Block> permits BlockTypeFilter.Blocks, BlockTypeFilter.Tag {
|
||||
|
||||
record Blocks(@NotNull List<Block> blocks) implements BlockTypeFilter {
|
||||
public Blocks {
|
||||
blocks = List.copyOf(blocks);
|
||||
}
|
||||
|
||||
public Blocks(@NotNull Block... blocks) {
|
||||
this(List.of(blocks));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(@NotNull Block block) {
|
||||
final int blockId = block.id();
|
||||
for (Block b : blocks) {
|
||||
if (blockId == b.id()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
record Tag(@NotNull net.minestom.server.gamedata.tags.Tag tag) implements BlockTypeFilter {
|
||||
private static final TagManager TAG_MANAGER = Objects.requireNonNull(MinecraftServer.getTagManager());
|
||||
|
||||
public Tag(@NotNull String namespaceId) {
|
||||
this(Objects.requireNonNull(TAG_MANAGER.getTag(net.minestom.server.gamedata.tags.Tag.BasicType.BLOCKS, namespaceId),
|
||||
"No such block tag: " + namespaceId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(Block block) {
|
||||
return tag.contains(block.namespace());
|
||||
}
|
||||
}
|
||||
|
||||
NetworkBuffer.Type<BlockTypeFilter> NETWORK_TYPE = new NetworkBuffer.Type<>() {
|
||||
@Override
|
||||
public void write(@NotNull NetworkBuffer buffer, BlockTypeFilter value) {
|
||||
switch (value) {
|
||||
case Blocks blocks -> {
|
||||
buffer.write(NetworkBuffer.VAR_INT, blocks.blocks.size() + 1);
|
||||
for (Block block : blocks.blocks) {
|
||||
buffer.write(NetworkBuffer.VAR_INT, block.id());
|
||||
}
|
||||
}
|
||||
case Tag tag -> {
|
||||
buffer.write(NetworkBuffer.VAR_INT, 0);
|
||||
buffer.write(NetworkBuffer.STRING, tag.tag.name());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockTypeFilter read(@NotNull NetworkBuffer buffer) {
|
||||
final int count = buffer.read(NetworkBuffer.VAR_INT) - 1;
|
||||
if (count == -1) {
|
||||
return new Tag(buffer.read(NetworkBuffer.STRING));
|
||||
} else {
|
||||
final List<Block> blocks = new ArrayList<>(count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
blocks.add(Block.fromBlockId(buffer.read(NetworkBuffer.VAR_INT)));
|
||||
}
|
||||
return new Blocks(blocks);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BinaryTagSerializer<BlockTypeFilter> NBT_TYPE = new BinaryTagSerializer<>() {
|
||||
@Override
|
||||
public @NotNull BinaryTag write(@NotNull BlockTypeFilter value) {
|
||||
return switch (value) {
|
||||
case Blocks blocks -> {
|
||||
ListBinaryTag.Builder<StringBinaryTag> builder = ListBinaryTag.builder(BinaryTagTypes.STRING);
|
||||
for (Block block : blocks.blocks) {
|
||||
builder.add(StringBinaryTag.stringBinaryTag(block.name()));
|
||||
}
|
||||
yield builder.build();
|
||||
}
|
||||
case Tag tag -> StringBinaryTag.stringBinaryTag("#" + tag.tag.name());
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BlockTypeFilter read(@NotNull BinaryTag tag) {
|
||||
return switch (tag) {
|
||||
case ListBinaryTag list -> {
|
||||
final List<Block> blocks = new ArrayList<>(list.size());
|
||||
for (BinaryTag binaryTag : list) {
|
||||
if (!(binaryTag instanceof StringBinaryTag string)) continue;
|
||||
blocks.add(Objects.requireNonNull(Block.fromNamespaceId(string.value())));
|
||||
}
|
||||
yield new Blocks(blocks);
|
||||
}
|
||||
case StringBinaryTag string -> {
|
||||
// Could be a tag or a block name depending if it starts with a #
|
||||
final String value = string.value();
|
||||
if (value.startsWith("#")) {
|
||||
yield new Tag(value.substring(1));
|
||||
} else {
|
||||
yield new Blocks(Objects.requireNonNull(Block.fromNamespaceId(value)));
|
||||
}
|
||||
}
|
||||
default -> throw new IllegalArgumentException("Invalid tag type: " + tag.type());
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue