net.minestom.server.registry.Registry Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of minestom-snapshots Show documentation
Show all versions of minestom-snapshots Show documentation
1.20.4 Lightweight Minecraft server
package net.minestom.server.registry;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.ToNumberPolicy;
import com.google.gson.stream.JsonReader;
import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.nbt.TagStringIOExt;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.Style;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
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.component.DataComponent;
import net.minestom.server.component.DataComponentMap;
import net.minestom.server.entity.EntitySpawnType;
import net.minestom.server.entity.EntityType;
import net.minestom.server.entity.EquipmentSlot;
import net.minestom.server.instance.block.Block;
import net.minestom.server.item.ItemComponent;
import net.minestom.server.item.Material;
import net.minestom.server.message.ChatTypeDecoration;
import net.minestom.server.sound.SoundEvent;
import net.minestom.server.utils.NamespaceID;
import net.minestom.server.utils.collection.ObjectArray;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
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 StaticProtocolObject} 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 DimensionTypeEntry dimensionType(String namespace, @NotNull Properties main) {
return new DimensionTypeEntry(namespace, main, null);
}
@ApiStatus.Internal
public static BiomeEntry biome(String namespace, Properties properties) {
return new BiomeEntry(namespace, properties, 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 FeatureFlagEntry featureFlag(String namespace, @NotNull Properties main) {
return new FeatureFlagEntry(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 AttributeEntry attribute(String namespace, @NotNull Properties main) {
return new AttributeEntry(namespace, main, null);
}
@ApiStatus.Internal
public static BannerPatternEntry bannerPattern(String namespace, @NotNull Properties main) {
return new BannerPatternEntry(namespace, main, null);
}
@ApiStatus.Internal
public static WolfVariantEntry wolfVariant(String namespace, @NotNull Properties main) {
return new WolfVariantEntry(namespace, main, null);
}
@ApiStatus.Internal
public static ChatTypeEntry chatType(String namespace, @NotNull Properties main) {
return new ChatTypeEntry(namespace, main, null);
}
@ApiStatus.Internal
public static EnchantmentEntry enchantment(String namespace, @NotNull Properties main) {
return new EnchantmentEntry(namespace, main, null);
}
@ApiStatus.Internal
public static PaintingVariantEntry paintingVariant(String namespace, @NotNull Properties main) {
return new PaintingVariantEntry(namespace, main, null);
}
@ApiStatus.Internal
public static JukeboxSongEntry jukeboxSong(String namespace, @NotNull Properties main) {
return new JukeboxSongEntry(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 createStaticContainer(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"),
FEATURE_FLAGS("feature_flags.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.json"),
ENTITY_TYPE_TAGS("tags/entity_type.json"),
FLUID_TAGS("tags/fluid.json"),
GAMEPLAY_TAGS("tags/game_event.json"),
ITEM_TAGS("tags/item.json"),
ENCHANTMENT_TAGS("tags/enchantment.json"),
DIMENSION_TYPES("dimension_types.json"),
BIOMES("biomes.json"),
ATTRIBUTES("attributes.json"),
BANNER_PATTERNS("banner_patterns.json"),
WOLF_VARIANTS("wolf_variants.json"),
CHAT_TYPES("chat_types.json"),
ENCHANTMENTS("enchantments.snbt"),
PAINTING_VARIANTS("painting_variants.json"),
JUKEBOX_SONGS("jukebox_songs.json");
private final String name;
Resource(String name) {
this.name = name;
}
public @NotNull String fileName() {
return 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 boolean redstoneConductor;
private final boolean signalSource;
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);
}
this.redstoneConductor = main.getBoolean("redstoneConductor");
this.signalSource = main.getBoolean("signalSource", false);
}
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 boolean isRedstoneConductor() {
return redstoneConductor;
}
public boolean isSignalSource() {
return signalSource;
}
public Shape collisionShape() {
return shape;
}
@Override
public Properties custom() {
return custom;
}
}
public record DimensionTypeEntry(
NamespaceID namespace,
boolean ultrawarm,
boolean natural,
double coordinateScale,
boolean hasSkylight,
boolean hasCeiling,
float ambientLight,
Long fixedTime,
boolean piglinSafe,
boolean bedWorks,
boolean respawnAnchorWorks,
boolean hasRaids,
int logicalHeight,
int minY,
int height,
String infiniburn,
String effects,
Properties custom
) implements Entry {
public DimensionTypeEntry(String namespace, Properties main, Properties custom) {
this(NamespaceID.from(namespace),
main.getBoolean("ultrawarm"),
main.getBoolean("natural"),
main.getDouble("coordinate_scale"),
main.getBoolean("has_skylight"),
main.getBoolean("has_ceiling"),
main.getFloat("ambient_light"),
(Long) main.asMap().get("fixed_time"),
main.getBoolean("piglin_safe"),
main.getBoolean("bed_works"),
main.getBoolean("respawn_anchor_works"),
main.getBoolean("has_raids"),
main.getInt("logical_height"),
main.getInt("min_y"),
main.getInt("height"),
main.getString("infiniburn"),
main.getString("effects"),
custom);
}
}
public static final class BiomeEntry implements Entry {
private final Properties custom;
private final NamespaceID namespace;
private final Integer foliageColor;
private final Integer grassColor;
private final Integer skyColor;
private final Integer waterColor;
private final Integer waterFogColor;
private final Integer fogColor;
private final float temperature;
private final float downfall;
private final boolean hasPrecipitation;
private BiomeEntry(String namespace, Properties main, Properties custom) {
this.custom = custom;
this.namespace = NamespaceID.from(namespace);
this.foliageColor = main.containsKey("foliageColor") ? main.getInt("foliageColor") : null;
this.grassColor = main.containsKey("grassColor") ? main.getInt("grassColor") : null;
this.skyColor = main.containsKey("skyColor") ? main.getInt("skyColor") : null;
this.waterColor = main.containsKey("waterColor") ? main.getInt("waterColor") : null;
this.waterFogColor = main.containsKey("waterFogColor") ? main.getInt("waterFogColor") : null;
this.fogColor = main.containsKey("fogColor") ? main.getInt("fogColor") : null;
this.temperature = (float) main.getDouble("temperature", 0.5F);
this.downfall = (float) main.getDouble("downfall", 0.5F);
this.hasPrecipitation = main.getBoolean("has_precipitation", true);
}
@Override
public Properties custom() {
return custom;
}
public @NotNull NamespaceID namespace() {
return namespace;
}
public @Nullable Integer foliageColor() {
return foliageColor;
}
public @Nullable Integer grassColor() {
return grassColor;
}
public @Nullable Integer skyColor() {
return skyColor;
}
public @Nullable Integer waterColor() {
return waterColor;
}
public @Nullable Integer waterFogColor() {
return waterFogColor;
}
public @Nullable Integer fogColor() {
return fogColor;
}
public float temperature() {
return temperature;
}
public float downfall() {
return downfall;
}
public boolean hasPrecipitation() {
return hasPrecipitation;
}
}
public static final class MaterialEntry implements Entry {
private final NamespaceID namespace;
private final Properties main;
private final int id;
private final String translationKey;
private final Supplier blockSupplier;
private DataComponentMap prototype;
private final EquipmentSlot equipmentSlot;
private final EntityType entityType;
private final Properties custom;
private MaterialEntry(String namespace, Properties main, Properties custom) {
this.main = main;
this.custom = custom;
this.namespace = NamespaceID.from(namespace);
this.id = main.getInt("id");
this.translationKey = main.getString("translationKey");
{
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;
}
}
{
final Properties spawnEggProperties = main.section("spawnEggProperties");
if (spawnEggProperties != null) {
this.entityType = EntityType.fromNamespaceId(spawnEggProperties.getString("entityType"));
} else {
this.entityType = null;
}
}
}
public @NotNull NamespaceID namespace() {
return namespace;
}
public int id() {
return id;
}
public @NotNull String translationKey() {
return translationKey;
}
public @Nullable Block block() {
return blockSupplier.get();
}
public @NotNull DataComponentMap prototype() {
if (prototype == null) {
try {
BinaryTagSerializer.Context context = new BinaryTagSerializer.ContextWithRegistries(MinecraftServer.process(), false);
DataComponentMap.Builder builder = DataComponentMap.builder();
for (Map.Entry entry : main.section("components")) {
//noinspection unchecked
DataComponent