Compare commits

...

49 Commits

Author SHA1 Message Date
Matt Worzala 546afff818
Merge 95c5f6675f into 5c23713c03 2024-04-25 12:49:37 +00:00
mworzala 95c5f6675f
fix: circular init :| 2024-04-25 08:49:31 -04:00
mworzala c29fe5a10a
fix: dont oom reading item component, stub banner pattern component 2024-04-25 08:45:38 -04:00
mworzala 3fcae5013d
chore: cleanup anvil test 2024-04-25 08:45:38 -04:00
mworzala 8aee481e1b
fix: light test and update eye height in view direction test 2024-04-25 08:45:38 -04:00
mworzala bcaa7df23d
feat: initial ArgumentItemStack reader, other minor fixes 2024-04-25 08:45:38 -04:00
mworzala 0400af8013
fix: do not allow writing air in declare recipes packet (added ItemStack.STRICT_NETWORK_TYPE to represent this) 2024-04-25 08:45:37 -04:00
mworzala 1cb848d1b9
fix: particle packet reorder 2024-04-25 08:45:37 -04:00
mworzala 1d54534562
fix: anvil not loading locations properly 2024-04-25 08:45:37 -04:00
mworzala adeb2c67a3
chore: update datagenerator 2024-04-25 08:45:37 -04:00
mworzala 9ff67e4025
chore: cicd trigger 2024-04-25 08:45:37 -04:00
mworzala 5036b72be4
chore: 1.20.5, disable some failing tests to get a functional build 2024-04-25 08:45:37 -04:00
mworzala 90d18c2a95
feat: partial anvil writing, update tests (still many broken) 2024-04-25 08:45:36 -04:00
mworzala 1246fa57d7
feat: anvil reading, other minor fixes 2024-04-25 08:45:36 -04:00
mworzala a50014ad8c
fix: do not send close inventory packet when opening a new inventory while one is open 2024-04-25 08:45:36 -04:00
mworzala 964b519146
chore: rebase fixes on inventory branch 2024-04-25 08:45:35 -04:00
mworzala ff0d121937
feat: generate RecipeType (for ids) 2024-04-25 08:45:35 -04:00
mworzala 49f8ae1223
feat: simplify sound events, fix update explosion packet 2024-04-25 08:45:35 -04:00
mworzala f9c835ed6c
feat: first draft of cookies 2024-04-25 08:45:35 -04:00
mworzala 4e6c15189c
feat: even more components (only 2 missing ones) 2024-04-25 08:45:35 -04:00
mworzala 2b974d95f9
feat: more components 2024-04-25 08:45:34 -04:00
mworzala 3fdd9ab9bb
chore: cleanup itemcomponenttype, update to datagen changes 2024-04-25 08:45:34 -04:00
mworzala c873d72f64
feat: functional components, but at what cost 2024-04-25 08:45:34 -04:00
mworzala f1f1246230
chore: trying to fix nightmare loop 2024-04-25 08:45:34 -04:00
mworzala a9e056a119
chore: more components 2024-04-25 08:45:34 -04:00
mworzala 690f828c3c
chore: more components 2024-04-25 08:45:33 -04:00
mworzala ac18b3fb38
chore: delete all old metadata classes for now, may return compatibility later 2024-04-25 08:45:33 -04:00
mworzala e8d4f79ee3
feat: first version of a bunch of components 2024-04-25 08:45:33 -04:00
mworzala d6d2e9c3e7
chore: rebase on adventure-nbt 2024-04-25 08:45:33 -04:00
mworzala 58367bb6de
fix: oops, health is before potion still 2024-04-25 08:45:33 -04:00
mworzala 721f70a28e
feat: joinable server 2024-04-25 08:45:31 -04:00
mworzala 62cb99a524
chore: basic nbt reader/writer for protocol while waiting for adventure 2024-04-25 08:45:11 -04:00
mworzala 5188c15245
feat: initial conversion to adventure nbt. no tests, no anvil 2024-04-25 08:45:11 -04:00
themode 41b442eab8
Remove fastutil pair 2024-04-25 08:45:11 -04:00
themode 8bb3ece803
Inline requireCreative 2024-04-25 08:45:11 -04:00
themode bb31cf8b20
Little simplification 2024-04-25 08:45:11 -04:00
GoldenStack 755f934448
Switch Click.Result to List<Click.Change> 2024-04-25 08:45:10 -04:00
GoldenStack d417f42b74
Fix player inventory size usage 2024-04-25 08:45:10 -04:00
GoldenStack 511c3d3e2e
Add Click.Change 2024-04-25 08:45:10 -04:00
themode ea7ef09be7
Move some constants out of PlayerInventory 2024-04-25 08:45:10 -04:00
themode f33565a82e
Store all processors in ClickProcessors 2024-04-25 08:45:10 -04:00
themode 401d5b57a7
More style 2024-04-25 08:45:10 -04:00
themode a350c29296
Remove Click's Inventory dependency 2024-04-25 08:45:09 -04:00
themode a0c915a5c9
Style change 2024-04-25 08:45:09 -04:00
GoldenStack 84ead9b25c
Remove fastutil references in API 2024-04-25 08:45:09 -04:00
GoldenStack 04893721b1
Inventory rework (88 squashed commits) 2024-04-25 08:45:09 -04:00
oglass 5c23713c03
Use PlayerInstanceEvent instead of PlayerEvent (#2102) 2024-04-24 16:27:42 +00:00
mworzala 129fd8ca0f
fix: properly return missing optvarint 2024-04-23 23:20:27 -04:00
mworzala f76d421744
fix: write metadata optvarint as varint + 1 || 0 2024-04-23 23:17:50 -04:00
375 changed files with 12008 additions and 9582 deletions

View File

@ -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)

View File

@ -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"),

View File

@ -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
);
}
}

View File

@ -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(),

View File

@ -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_", "");
}
}

View File

@ -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();

View File

@ -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));
}

View File

@ -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()
));
}

View File

@ -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));
}
});
}
}
}

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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

View File

@ -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]

View File

@ -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;

View File

@ -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");

View File

@ -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");
}

View File

@ -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");

View File

@ -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");
}

View File

@ -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");
}

View File

@ -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");

View File

@ -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");
}

View File

@ -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");
}

View File

@ -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");
}

View File

@ -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();
}
}

View File

@ -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() {}
}

View File

@ -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";

View File

@ -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

View File

@ -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);
}
/**

View File

@ -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.

View File

@ -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()));
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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));

View File

@ -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);
}
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
/**

View File

@ -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();
}
/**

View File

@ -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)
);
}
}

View File

@ -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;
};
}
}

View File

@ -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());

View File

@ -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);
}
}

View File

@ -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 {

View File

@ -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();

View File

@ -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

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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());

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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();
}
}

View File

@ -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()
);
}
}

View File

@ -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));
}
}

View File

@ -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));
}

View File

@ -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();
}
}

View File

@ -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() {

View File

@ -12,6 +12,8 @@ public class WolfMeta extends TameableAnimalMeta {
super(entity, metadata);
}
//todo variant
public boolean isBegging() {
return super.metadata.getIndex(OFFSET, false);
}

View File

@ -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() {

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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() {

View File

@ -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

View File

@ -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

View File

@ -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));
}
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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() {

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -1,15 +1,14 @@
package net.minestom.server.event.player;
import net.minestom.server.entity.Player;
import net.minestom.server.event.trait.PlayerEvent;
import net.minestom.server.event.trait.PlayerInstanceEvent;
import net.minestom.server.instance.Instance;
import org.jetbrains.annotations.NotNull;
/**
* Called when a new instance is set for a player.
*/
public class PlayerSpawnEvent implements PlayerEvent {
public class PlayerSpawnEvent implements PlayerInstanceEvent {
private final Player player;
private final Instance spawnInstance;
private final boolean firstSpawn;
@ -21,11 +20,12 @@ public class PlayerSpawnEvent implements PlayerEvent {
}
/**
* Gets the entity new instance.
* Gets the player's new instance.
*
* @return the instance
*/
@NotNull
@Deprecated
public Instance getSpawnInstance() {
return spawnInstance;
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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
*/

View File

@ -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();
}

View File

@ -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());
}
}
}
}

View File

@ -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);
}

View File

@ -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() {

View File

@ -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);
}

View File

@ -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

View File

@ -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);

View File

@ -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));
}
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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() {

View File

@ -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() {

View File

@ -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");
}
}

View File

@ -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());
};
}
};
}

View File

@ -0,0 +1,136 @@
package net.minestom.server.instance.block.predicate;
import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.StringBinaryTag;
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.HashMap;
import java.util.Map;
public record PropertiesPredicate(@NotNull Map<String, ValuePredicate> properties) {
public static final NetworkBuffer.Type<PropertiesPredicate> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, PropertiesPredicate value) {
buffer.write(NetworkBuffer.VAR_INT, value.properties.size());
for (Map.Entry<String, ValuePredicate> entry : value.properties.entrySet()) {
buffer.write(NetworkBuffer.STRING, entry.getKey());
buffer.write(ValuePredicate.NETWORK_TYPE, entry.getValue());
}
}
@Override
public PropertiesPredicate read(@NotNull NetworkBuffer buffer) {
int size = buffer.read(NetworkBuffer.VAR_INT);
Map<String, ValuePredicate> properties = new HashMap<>(size);
for (int i = 0; i < size; i++) {
properties.put(buffer.read(NetworkBuffer.STRING), buffer.read(ValuePredicate.NETWORK_TYPE));
}
return new PropertiesPredicate(properties);
}
};
public static final BinaryTagSerializer<PropertiesPredicate> NBT_TYPE = BinaryTagSerializer.COMPOUND.map(
tag -> {
Map<String, ValuePredicate> properties = new HashMap<>();
for (Map.Entry<String, ? extends BinaryTag> entry : tag) {
properties.put(entry.getKey(), ValuePredicate.NBT_TYPE.read(entry.getValue()));
}
return new PropertiesPredicate(properties);
},
value -> {
CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder();
for (Map.Entry<String, ValuePredicate> entry : value.properties.entrySet()) {
builder.put(entry.getKey(), ValuePredicate.NBT_TYPE.write(entry.getValue()));
}
return builder.build();
}
);
public static @NotNull PropertiesPredicate exact(@NotNull String key, @NotNull String value) {
return new PropertiesPredicate(Map.of(key, new ValuePredicate.Exact(value)));
}
public PropertiesPredicate {
properties = Map.copyOf(properties);
}
public sealed interface ValuePredicate permits ValuePredicate.Exact, ValuePredicate.Range {
record Exact(@Nullable String value) implements ValuePredicate {
public static final NetworkBuffer.Type<Exact> NETWORK_TYPE = NetworkBuffer.STRING.map(Exact::new, Exact::value);
public static final BinaryTagSerializer<Exact> NBT_TYPE = BinaryTagSerializer.STRING.map(Exact::new, Exact::value);
}
record Range(@Nullable String min, @Nullable String max) implements ValuePredicate {
public static final NetworkBuffer.Type<Range> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, Range value) {
buffer.writeOptional(NetworkBuffer.STRING, value.min);
buffer.writeOptional(NetworkBuffer.STRING, value.max);
}
@Override
public Range read(@NotNull NetworkBuffer buffer) {
return new Range(buffer.readOptional(NetworkBuffer.STRING), buffer.readOptional(NetworkBuffer.STRING));
}
};
public static final BinaryTagSerializer<Range> NBT_TYPE = BinaryTagSerializer.COMPOUND.map(
tag -> new Range(
tag.get("min") instanceof StringBinaryTag string ? string.value() : null,
tag.get("max") instanceof StringBinaryTag string ? string.value() : null),
value -> {
CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder();
if (value.min != null) builder.putString("min", value.min);
if (value.max != null) builder.putString("max", value.max);
return builder.build();
}
);
}
NetworkBuffer.Type<ValuePredicate> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, ValuePredicate value) {
switch (value) {
case Exact exact -> {
buffer.write(NetworkBuffer.BOOLEAN, true);
buffer.write(Exact.NETWORK_TYPE, exact);
}
case Range range -> {
buffer.write(NetworkBuffer.BOOLEAN, false);
buffer.write(Range.NETWORK_TYPE, range);
}
}
}
@Override
public ValuePredicate read(@NotNull NetworkBuffer buffer) {
return buffer.read(NetworkBuffer.BOOLEAN) ? buffer.read(Exact.NETWORK_TYPE) : buffer.read(Range.NETWORK_TYPE);
}
};
BinaryTagSerializer<ValuePredicate> NBT_TYPE = new BinaryTagSerializer<>() {
@Override
public @NotNull BinaryTag write(@NotNull ValuePredicate value) {
return switch (value) {
case Exact exact -> Exact.NBT_TYPE.write(exact);
case Range range -> Range.NBT_TYPE.write(range);
};
}
@Override
public @NotNull ValuePredicate read(@NotNull BinaryTag tag) {
if (tag instanceof StringBinaryTag) {
return Exact.NBT_TYPE.read(tag);
} else {
return Range.NBT_TYPE.read(tag);
}
}
};
}
}

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