
net.minestom.server.registry.Registry Maven / Gradle / Ivy
package net.minestom.server.registry;
import com.google.gson.ToNumberPolicy;
import com.google.gson.stream.JsonReader;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.json.JSONComponentSerializer;
import net.minestom.server.MinecraftServer;
import net.minestom.server.collision.BoundingBox;
import net.minestom.server.collision.CollisionUtils;
import net.minestom.server.collision.Shape;
import net.minestom.server.entity.EntitySpawnType;
import net.minestom.server.entity.EquipmentSlot;
import net.minestom.server.instance.block.Block;
import net.minestom.server.item.Material;
import net.minestom.server.utils.NamespaceID;
import net.minestom.server.utils.collection.ObjectArray;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
* Handles registry data, used by {@link ProtocolObject} implementations and is strictly internal.
* Use at your own risk.
*/
public final class Registry {
@ApiStatus.Internal
public static BlockEntry block(String namespace, @NotNull Properties main) {
return new BlockEntry(namespace, main, null);
}
@ApiStatus.Internal
public static MaterialEntry material(String namespace, @NotNull Properties main) {
return new MaterialEntry(namespace, main, null);
}
@ApiStatus.Internal
public static EntityEntry entity(String namespace, @NotNull Properties main) {
return new EntityEntry(namespace, main, null);
}
@ApiStatus.Internal
public static EnchantmentEntry enchantment(String namespace, @NotNull Properties main) {
return new EnchantmentEntry(namespace, main, null);
}
@ApiStatus.Internal
public static PotionEffectEntry potionEffect(String namespace, @NotNull Properties main) {
return new PotionEffectEntry(namespace, main, null);
}
@ApiStatus.Internal
public static DamageTypeEntry damageType(String namespace, @NotNull Properties main) {
return new DamageTypeEntry(namespace, main, null);
}
@ApiStatus.Internal
public static TrimMaterialEntry trimMaterial(String namespace, @NotNull Properties main) {
return new TrimMaterialEntry(namespace, main, null);
}
@ApiStatus.Internal
public static TrimPatternEntry trimPattern(String namespace, @NotNull Properties main) {
return new TrimPatternEntry(namespace, main, null);
}
@ApiStatus.Internal
public static Map> load(Resource resource) {
Map> map = new HashMap<>();
try (InputStream resourceStream = Registry.class.getClassLoader().getResourceAsStream(resource.name)) {
Check.notNull(resourceStream, "Resource {0} does not exist!", resource);
try (JsonReader reader = new JsonReader(new InputStreamReader(resourceStream))) {
reader.beginObject();
while (reader.hasNext()) map.put(reader.nextName(), (Map) readObject(reader));
reader.endObject();
}
} catch (IOException e) {
MinecraftServer.getExceptionManager().handleException(e);
}
return map;
}
@ApiStatus.Internal
public static Container createContainer(Resource resource, Container.Loader loader) {
var entries = Registry.load(resource);
Map namespaces = new HashMap<>(entries.size());
ObjectArray ids = ObjectArray.singleThread(entries.size());
for (var entry : entries.entrySet()) {
final String namespace = entry.getKey();
final Properties properties = Properties.fromMap(entry.getValue());
final T value = loader.get(namespace, properties);
ids.set(value.id(), value);
namespaces.put(value.name(), value);
}
return new Container<>(resource, namespaces, ids);
}
@ApiStatus.Internal
public record Container(Resource resource,
Map namespaces,
ObjectArray ids) {
public Container {
namespaces = Map.copyOf(namespaces);
ids.trim();
}
public T get(@NotNull String namespace) {
return namespaces.get(namespace);
}
public T getSafe(@NotNull String namespace) {
return get(namespace.contains(":") ? namespace : "minecraft:" + namespace);
}
public T getId(int id) {
return ids.get(id);
}
public int toId(@NotNull String namespace) {
return get(namespace).id();
}
public Collection values() {
return namespaces.values();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Container> container)) return false;
return resource == container.resource;
}
@Override
public int hashCode() {
return Objects.hash(resource);
}
public interface Loader {
T get(String namespace, Properties properties);
}
}
@ApiStatus.Internal
public enum Resource {
BLOCKS("blocks.json"),
ITEMS("items.json"),
ENTITIES("entities.json"),
ENCHANTMENTS("enchantments.json"),
SOUNDS("sounds.json"),
COMMAND_ARGUMENTS("command_arguments.json"),
STATISTICS("custom_statistics.json"),
POTION_EFFECTS("potion_effects.json"),
POTION_TYPES("potions.json"),
PARTICLES("particles.json"),
DAMAGE_TYPES("damage_types.json"),
TRIM_MATERIALS("trim_materials.json"),
TRIM_PATTERNS("trim_patterns.json"),
BLOCK_TAGS("tags/block_tags.json"),
ENTITY_TYPE_TAGS("tags/entity_type_tags.json"),
FLUID_TAGS("tags/fluid_tags.json"),
GAMEPLAY_TAGS("tags/gameplay_tags.json"),
ITEM_TAGS("tags/item_tags.json");
private final String name;
Resource(String name) {
this.name = name;
}
}
public static final class BlockEntry implements Entry {
private final NamespaceID namespace;
private final int id;
private final int stateId;
private final String translationKey;
private final double hardness;
private final double explosionResistance;
private final double friction;
private final double speedFactor;
private final double jumpFactor;
private final boolean air;
private final boolean solid;
private final boolean liquid;
private final boolean occludes;
private final int lightEmission;
private final boolean replaceable;
private final String blockEntity;
private final int blockEntityId;
private final Supplier materialSupplier;
private final Shape shape;
private final Properties custom;
private BlockEntry(String namespace, Properties main, Properties custom) {
this.custom = custom;
this.namespace = NamespaceID.from(namespace);
this.id = main.getInt("id");
this.stateId = main.getInt("stateId");
this.translationKey = main.getString("translationKey");
this.hardness = main.getDouble("hardness");
this.explosionResistance = main.getDouble("explosionResistance");
this.friction = main.getDouble("friction");
this.speedFactor = main.getDouble("speedFactor", 1);
this.jumpFactor = main.getDouble("jumpFactor", 1);
this.air = main.getBoolean("air", false);
this.solid = main.getBoolean("solid");
this.liquid = main.getBoolean("liquid", false);
this.occludes = main.getBoolean("occludes", true);
this.lightEmission = main.getInt("lightEmission", 0);
this.replaceable = main.getBoolean("replaceable", false);
{
Properties blockEntity = main.section("blockEntity");
if (blockEntity != null) {
this.blockEntity = blockEntity.getString("namespace");
this.blockEntityId = blockEntity.getInt("id");
} else {
this.blockEntity = null;
this.blockEntityId = 0;
}
}
{
final String materialNamespace = main.getString("correspondingItem", null);
this.materialSupplier = materialNamespace != null ? () -> Material.fromNamespaceId(materialNamespace) : () -> null;
}
{
final String collision = main.getString("collisionShape");
final String occlusion = main.getString("occlusionShape");
this.shape = CollisionUtils.parseBlockShape(collision, occlusion, this);
}
}
public @NotNull NamespaceID namespace() {
return namespace;
}
public int id() {
return id;
}
public int stateId() {
return stateId;
}
public String translationKey() {
return translationKey;
}
public double hardness() {
return hardness;
}
public double explosionResistance() {
return explosionResistance;
}
public double friction() {
return friction;
}
public double speedFactor() {
return speedFactor;
}
public double jumpFactor() {
return jumpFactor;
}
public boolean isAir() {
return air;
}
public boolean isSolid() {
return solid;
}
public boolean isLiquid() {
return liquid;
}
public boolean occludes() {
return occludes;
}
public int lightEmission() {
return lightEmission;
}
public boolean isReplaceable() {
return replaceable;
}
public boolean isBlockEntity() {
return blockEntity != null;
}
public @Nullable String blockEntity() {
return blockEntity;
}
public int blockEntityId() {
return blockEntityId;
}
public @Nullable Material material() {
return materialSupplier.get();
}
public Shape collisionShape() {
return shape;
}
@Override
public Properties custom() {
return custom;
}
}
public static final class MaterialEntry implements Entry {
private final NamespaceID namespace;
private final int id;
private final String translationKey;
private final int maxStackSize;
private final int maxDamage;
private final boolean isFood;
private final Supplier blockSupplier;
private final EquipmentSlot equipmentSlot;
private final Properties custom;
private MaterialEntry(String namespace, Properties main, Properties custom) {
this.custom = custom;
this.namespace = NamespaceID.from(namespace);
this.id = main.getInt("id");
this.translationKey = main.getString("translationKey");
this.maxStackSize = main.getInt("maxStackSize", 64);
this.maxDamage = main.getInt("maxDamage", 0);
this.isFood = main.getBoolean("edible", false);
{
final String blockNamespace = main.getString("correspondingBlock", null);
this.blockSupplier = blockNamespace != null ? () -> Block.fromNamespaceId(blockNamespace) : () -> null;
}
{
final Properties armorProperties = main.section("armorProperties");
if (armorProperties != null) {
switch (armorProperties.getString("slot")) {
case "feet" -> this.equipmentSlot = EquipmentSlot.BOOTS;
case "legs" -> this.equipmentSlot = EquipmentSlot.LEGGINGS;
case "chest" -> this.equipmentSlot = EquipmentSlot.CHESTPLATE;
case "head" -> this.equipmentSlot = EquipmentSlot.HELMET;
default -> this.equipmentSlot = null;
}
} else {
this.equipmentSlot = null;
}
}
}
public @NotNull NamespaceID namespace() {
return namespace;
}
public int id() {
return id;
}
public String translationKey() {
return translationKey;
}
public int maxStackSize() {
return maxStackSize;
}
public int maxDamage() {
return maxDamage;
}
public boolean isFood() {
return isFood;
}
public @Nullable Block block() {
return blockSupplier.get();
}
public boolean isArmor() {
return equipmentSlot != null;
}
public @Nullable EquipmentSlot equipmentSlot() {
return equipmentSlot;
}
@Override
public Properties custom() {
return custom;
}
}
public record EntityEntry(NamespaceID namespace, int id,
String translationKey,
double width, double height,
double drag, double acceleration,
EntitySpawnType spawnType,
BoundingBox boundingBox,
Properties custom) implements Entry {
public EntityEntry(String namespace, Properties main, Properties custom) {
this(NamespaceID.from(namespace),
main.getInt("id"),
main.getString("translationKey"),
main.getDouble("width"),
main.getDouble("height"),
main.getDouble("drag", 0.02),
main.getDouble("acceleration", 0.08),
EntitySpawnType.valueOf(main.getString("packetType").toUpperCase(Locale.ROOT)),
new BoundingBox(
main.getDouble("width"),
main.getDouble("height"),
main.getDouble("width")),
custom
);
}
}
public record DamageTypeEntry(NamespaceID namespace, float exhaustion,
String messageId,
String scaling,
@Nullable String effects,
@Nullable String deathMessageType,
Properties custom) implements Entry {
public DamageTypeEntry(String namespace, Properties main, Properties custom) {
this(NamespaceID.from(namespace),
(float) main.getDouble("exhaustion"),
main.getString("message_id"),
main.getString("scaling"),
main.getString("effects"),
main.getString("death_message_type"),
custom);
}
}
public record TrimMaterialEntry(@NotNull NamespaceID namespace,
@NotNull String assetName,
@NotNull Material ingredient,
float itemModelIndex,
@NotNull Map overrideArmorMaterials,
@NotNull Component description,
Properties custom) implements Entry {
public TrimMaterialEntry(@NotNull String namespace, @NotNull Properties main, Properties custom) {
this(
NamespaceID.from(namespace),
main.getString("asset_name"),
Objects.requireNonNull(Material.fromNamespaceId(main.getString("ingredient"))),
(float) main.getDouble("item_model_index"),
Objects.requireNonNullElse(main.section("override_armor_materials"),new PropertiesMap(Map.of()))
.asMap().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> (String) entry.getValue())),
JSONComponentSerializer.json().deserialize(main.section("description").toString()),
custom
);
}
}
public record TrimPatternEntry(@NotNull NamespaceID namespace,
@NotNull NamespaceID assetID,
@NotNull Material template,
@NotNull Component description,
boolean decal,
Properties custom) implements Entry {
public TrimPatternEntry(@NotNull String namespace, @NotNull Properties main, Properties custom) {
this(
NamespaceID.from(namespace),
NamespaceID.from(main.getString("asset_id")),
Objects.requireNonNull(Material.fromNamespaceId(main.getString("template_item"))),
JSONComponentSerializer.json().deserialize(main.section("description").toString()),
main.getBoolean("decal"),
custom
);
}
}
public record EnchantmentEntry(NamespaceID namespace, int id,
String translationKey,
double maxLevel,
boolean isCursed,
boolean isDiscoverable,
boolean isTradeable,
boolean isTreasureOnly,
Properties custom) implements Entry {
public EnchantmentEntry(String namespace, Properties main, Properties custom) {
this(NamespaceID.from(namespace),
main.getInt("id"),
main.getString("translationKey"),
main.getDouble("maxLevel"),
main.getBoolean("curse", false),
main.getBoolean("discoverable", true),
main.getBoolean("tradeable", true),
main.getBoolean("treasureOnly", false),
custom);
}
}
public record PotionEffectEntry(NamespaceID namespace, int id,
String translationKey,
int color,
boolean isInstantaneous,
Properties custom) implements Entry {
public PotionEffectEntry(String namespace, Properties main, Properties custom) {
this(NamespaceID.from(namespace),
main.getInt("id"),
main.getString("translationKey"),
main.getInt("color"),
main.getBoolean("instantaneous"),
custom);
}
}
public interface Entry {
@ApiStatus.Experimental
Properties custom();
}
private static Object readObject(JsonReader reader) throws IOException {
return switch (reader.peek()) {
case BEGIN_ARRAY -> {
List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy