Optionally load vanilla biomes (#1988)

* Add biomes from vanilla

* cleanup

* rework biomes

* nullability

* getByName string

* expose vanilla biomes

* not null

* before rename

* rename

* nbt cache

* fix

* fix

* fix

* final on vanilla biome
This commit is contained in:
iam 2024-02-12 15:25:46 -05:00 committed by GitHub
parent 1b9e186c3a
commit 7320437640
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
46 changed files with 762 additions and 323 deletions

View File

@ -27,6 +27,7 @@ public class Generators {
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("potion_effects.json"), "net.minestom.server.potion", "PotionEffect", "PotionEffectImpl", "PotionEffects");
generator.generate(resource("potions.json"), "net.minestom.server.potion", "PotionType", "PotionTypeImpl", "PotionTypes");

View File

@ -36,6 +36,7 @@ public class Main {
MinecraftServer.setCompressionThreshold(0);
MinecraftServer minecraftServer = MinecraftServer.init();
MinecraftServer.getBiomeManager().loadVanillaBiomes();
BlockManager blockManager = MinecraftServer.getBlockManager();
blockManager.registerBlockPlacementRule(new DripstonePlacementRule());

View File

@ -3,7 +3,7 @@ metadata.format.version = "1.1"
[versions]
# Important dependencies
data = "1.20.4-rv2"
data = "1.20.4-rv3"
adventure = "4.15.0"
kotlin = "1.7.22"
dependencyGetter = "v1.0.1"

View File

@ -0,0 +1,135 @@
package net.minestom.server.world.biomes;
/**
* Code autogenerated, do not edit!
*/
@SuppressWarnings("unused")
interface Biomes {
Biome SNOWY_SLOPES = BiomeImpl.get("minecraft:snowy_slopes");
Biome OLD_GROWTH_PINE_TAIGA = BiomeImpl.get("minecraft:old_growth_pine_taiga");
Biome MUSHROOM_FIELDS = BiomeImpl.get("minecraft:mushroom_fields");
Biome TAIGA = BiomeImpl.get("minecraft:taiga");
Biome DEEP_OCEAN = BiomeImpl.get("minecraft:deep_ocean");
Biome ERODED_BADLANDS = BiomeImpl.get("minecraft:eroded_badlands");
Biome FROZEN_RIVER = BiomeImpl.get("minecraft:frozen_river");
Biome END_HIGHLANDS = BiomeImpl.get("minecraft:end_highlands");
Biome CHERRY_GROVE = BiomeImpl.get("minecraft:cherry_grove");
Biome SUNFLOWER_PLAINS = BiomeImpl.get("minecraft:sunflower_plains");
Biome BIRCH_FOREST = BiomeImpl.get("minecraft:birch_forest");
Biome WINDSWEPT_HILLS = BiomeImpl.get("minecraft:windswept_hills");
Biome BAMBOO_JUNGLE = BiomeImpl.get("minecraft:bamboo_jungle");
Biome WOODED_BADLANDS = BiomeImpl.get("minecraft:wooded_badlands");
Biome BADLANDS = BiomeImpl.get("minecraft:badlands");
Biome SAVANNA_PLATEAU = BiomeImpl.get("minecraft:savanna_plateau");
Biome BEACH = BiomeImpl.get("minecraft:beach");
Biome DARK_FOREST = BiomeImpl.get("minecraft:dark_forest");
Biome STONY_PEAKS = BiomeImpl.get("minecraft:stony_peaks");
Biome MANGROVE_SWAMP = BiomeImpl.get("minecraft:mangrove_swamp");
Biome SPARSE_JUNGLE = BiomeImpl.get("minecraft:sparse_jungle");
Biome LUKEWARM_OCEAN = BiomeImpl.get("minecraft:lukewarm_ocean");
Biome RIVER = BiomeImpl.get("minecraft:river");
Biome STONY_SHORE = BiomeImpl.get("minecraft:stony_shore");
Biome WARPED_FOREST = BiomeImpl.get("minecraft:warped_forest");
Biome SNOWY_PLAINS = BiomeImpl.get("minecraft:snowy_plains");
Biome DRIPSTONE_CAVES = BiomeImpl.get("minecraft:dripstone_caves");
Biome SNOWY_TAIGA = BiomeImpl.get("minecraft:snowy_taiga");
Biome GROVE = BiomeImpl.get("minecraft:grove");
Biome SWAMP = BiomeImpl.get("minecraft:swamp");
Biome JAGGED_PEAKS = BiomeImpl.get("minecraft:jagged_peaks");
Biome COLD_OCEAN = BiomeImpl.get("minecraft:cold_ocean");
Biome FOREST = BiomeImpl.get("minecraft:forest");
Biome LUSH_CAVES = BiomeImpl.get("minecraft:lush_caves");
Biome BASALT_DELTAS = BiomeImpl.get("minecraft:basalt_deltas");
Biome DEEP_COLD_OCEAN = BiomeImpl.get("minecraft:deep_cold_ocean");
Biome ICE_SPIKES = BiomeImpl.get("minecraft:ice_spikes");
Biome END_MIDLANDS = BiomeImpl.get("minecraft:end_midlands");
Biome FROZEN_OCEAN = BiomeImpl.get("minecraft:frozen_ocean");
Biome DESERT = BiomeImpl.get("minecraft:desert");
Biome DEEP_FROZEN_OCEAN = BiomeImpl.get("minecraft:deep_frozen_ocean");
Biome WINDSWEPT_FOREST = BiomeImpl.get("minecraft:windswept_forest");
Biome JUNGLE = BiomeImpl.get("minecraft:jungle");
Biome OCEAN = BiomeImpl.get("minecraft:ocean");
Biome OLD_GROWTH_SPRUCE_TAIGA = BiomeImpl.get("minecraft:old_growth_spruce_taiga");
Biome SNOWY_BEACH = BiomeImpl.get("minecraft:snowy_beach");
Biome WINDSWEPT_SAVANNA = BiomeImpl.get("minecraft:windswept_savanna");
Biome END_BARRENS = BiomeImpl.get("minecraft:end_barrens");
Biome WARM_OCEAN = BiomeImpl.get("minecraft:warm_ocean");
Biome DEEP_LUKEWARM_OCEAN = BiomeImpl.get("minecraft:deep_lukewarm_ocean");
Biome FLOWER_FOREST = BiomeImpl.get("minecraft:flower_forest");
Biome SOUL_SAND_VALLEY = BiomeImpl.get("minecraft:soul_sand_valley");
Biome NETHER_WASTES = BiomeImpl.get("minecraft:nether_wastes");
Biome FROZEN_PEAKS = BiomeImpl.get("minecraft:frozen_peaks");
Biome THE_END = BiomeImpl.get("minecraft:the_end");
Biome SMALL_END_ISLANDS = BiomeImpl.get("minecraft:small_end_islands");
Biome OLD_GROWTH_BIRCH_FOREST = BiomeImpl.get("minecraft:old_growth_birch_forest");
Biome CRIMSON_FOREST = BiomeImpl.get("minecraft:crimson_forest");
Biome THE_VOID = BiomeImpl.get("minecraft:the_void");
Biome DEEP_DARK = BiomeImpl.get("minecraft:deep_dark");
Biome MEADOW = BiomeImpl.get("minecraft:meadow");
Biome WINDSWEPT_GRAVELLY_HILLS = BiomeImpl.get("minecraft:windswept_gravelly_hills");
Biome SAVANNA = BiomeImpl.get("minecraft:savanna");
Biome PLAINS = BiomeImpl.get("minecraft:plains");
}

View File

@ -7,7 +7,7 @@ import net.minestom.server.command.builder.CommandExecutor;
import net.minestom.server.command.builder.arguments.minecraft.SuggestionType;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import net.minestom.server.command.builder.suggestion.SuggestionCallback;
import net.minestom.server.registry.ProtocolObject;
import net.minestom.server.registry.StaticProtocolObject;
import net.minestom.server.registry.Registry;
import net.minestom.server.utils.NamespaceID;
import org.jetbrains.annotations.ApiStatus;
@ -30,10 +30,10 @@ import java.util.function.Supplier;
*/
public abstract class Argument<T> {
@ApiStatus.Internal
public static final Registry.Container<ArgumentImpl> CONTAINER = Registry.createContainer(Registry.Resource.COMMAND_ARGUMENTS,
public static final Registry.Container<ArgumentImpl> CONTAINER = Registry.createStaticContainer(Registry.Resource.COMMAND_ARGUMENTS,
(namespace, properties) -> new ArgumentImpl(NamespaceID.from(namespace), properties.getInt("id")));
record ArgumentImpl(NamespaceID namespace, int id) implements ProtocolObject {
record ArgumentImpl(NamespaceID namespace, int id) implements StaticProtocolObject {
@Override
public String toString() {
return name();

View File

@ -1,6 +1,6 @@
package net.minestom.server.entity;
import net.minestom.server.registry.ProtocolObject;
import net.minestom.server.registry.StaticProtocolObject;
import net.minestom.server.registry.Registry;
import net.minestom.server.utils.NamespaceID;
import org.jetbrains.annotations.Contract;
@ -9,7 +9,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.Collection;
public sealed interface EntityType extends ProtocolObject, EntityTypes permits EntityTypeImpl {
public sealed interface EntityType extends StaticProtocolObject, EntityTypes permits EntityTypeImpl {
/**
* Returns the entity registry.
*

View File

@ -42,7 +42,7 @@ 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.createContainer(Registry.Resource.ENTITIES,
private static final Registry.Container<EntityType> CONTAINER = Registry.createStaticContainer(Registry.Resource.ENTITIES,
(namespace, properties) -> new EntityTypeImpl(Registry.entity(namespace, properties)));
static final Map<String, BiFunction<Entity, Metadata, EntityMeta>> ENTITY_META_SUPPLIER = createMetaMap();

View File

@ -1,6 +1,6 @@
package net.minestom.server.entity.damage;
import net.minestom.server.registry.ProtocolObject;
import net.minestom.server.registry.StaticProtocolObject;
import net.minestom.server.registry.Registry;
import net.minestom.server.utils.NamespaceID;
import org.jetbrains.annotations.Contract;
@ -10,7 +10,7 @@ import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import java.util.Collection;
public sealed interface DamageType extends ProtocolObject, DamageTypes permits DamageTypeImpl {
public sealed interface DamageType extends StaticProtocolObject, DamageTypes permits DamageTypeImpl {
/**
* Returns the damage type registry.
*

View File

@ -16,7 +16,7 @@ record DamageTypeImpl(Registry.DamageTypeEntry registry, int id) implements Dama
static {
AtomicInteger i = new AtomicInteger();
CONTAINER = Registry.createContainer(Registry.Resource.DAMAGE_TYPES,
CONTAINER = Registry.createStaticContainer(Registry.Resource.DAMAGE_TYPES,
(namespace, properties) -> new DamageTypeImpl(Registry.damageType(namespace, properties), i.getAndIncrement()));
}

View File

@ -34,7 +34,7 @@ import java.util.concurrent.ConcurrentHashMap;
public class AnvilLoader implements IChunkLoader {
private final static Logger LOGGER = LoggerFactory.getLogger(AnvilLoader.class);
private static final Biome BIOME = Biome.PLAINS;
private final static Biome PLAINS = MinecraftServer.getBiomeManager().getByName(NamespaceID.from("minecraft:plains"));
private final Map<String, RegionFile> alreadyLoaded = new ConcurrentHashMap<>();
private final Path path;
@ -189,7 +189,7 @@ public class AnvilLoader implements IChunkLoader {
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)), BIOME));
Objects.requireNonNullElse(MinecraftServer.getBiomeManager().getByName(NamespaceID.from(n)), PLAINS));
chunk.setBiome(finalX, finalY, finalZ, biome);
}
}
@ -205,7 +205,7 @@ public class AnvilLoader implements IChunkLoader {
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)), BIOME));
Objects.requireNonNullElse(MinecraftServer.getBiomeManager().getByName(NamespaceID.from(n)), PLAINS));
chunk.setBiome(finalX, finalY, finalZ, biome);
}
}
@ -400,7 +400,7 @@ public class AnvilLoader implements IChunkLoader {
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().asString();
final String biomeName = biome.name();
biomePalette.increaseReference(biomeName);
palettedBiomes[biomeIndex] = biomePalette.getPaletteIndex(biomeName);

View File

@ -24,6 +24,7 @@ import net.minestom.server.utils.MathUtils;
import net.minestom.server.utils.ObjectPool;
import net.minestom.server.utils.chunk.ChunkUtils;
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;
@ -51,6 +52,7 @@ public class DynamicChunk extends Chunk {
private long lastChange;
final CachedPacket chunkCache = new CachedPacket(this::createChunkPacket);
private static final BiomeManager BIOME_MANAGER = MinecraftServer.getBiomeManager();
public DynamicChunk(@NotNull Instance instance, int chunkX, int chunkZ) {
super(instance, chunkX, chunkZ, true);
@ -124,10 +126,14 @@ public class DynamicChunk extends Chunk {
assertLock();
this.chunkCache.invalidate();
Section section = getSectionAt(y);
var id = BIOME_MANAGER.getId(biome);
if (id == -1) throw new IllegalStateException("Biome has not been registered: " + biome.namespace());
section.biomePalette().set(
toSectionRelativeCoordinate(x) / 4,
toSectionRelativeCoordinate(y) / 4,
toSectionRelativeCoordinate(z) / 4, biome.id());
toSectionRelativeCoordinate(z) / 4, id);
}
@Override
@ -180,7 +186,13 @@ public class DynamicChunk extends Chunk {
final Section section = getSectionAt(y);
final int id = section.biomePalette()
.get(toSectionRelativeCoordinate(x) / 4, toSectionRelativeCoordinate(y) / 4, toSectionRelativeCoordinate(z) / 4);
return MinecraftServer.getBiomeManager().getById(id);
Biome biome = BIOME_MANAGER.getById(id);
if (biome == null) {
throw new IllegalStateException("Biome with id " + id + " is not registered");
}
return biome;
}
@Override

View File

@ -2,6 +2,7 @@ package net.minestom.server.instance;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.instance.block.Block;
@ -9,6 +10,7 @@ import net.minestom.server.instance.generator.GenerationUnit;
import net.minestom.server.instance.generator.UnitModifier;
import net.minestom.server.instance.palette.Palette;
import net.minestom.server.world.biomes.Biome;
import net.minestom.server.world.biomes.BiomeManager;
import org.jetbrains.annotations.NotNull;
import java.util.List;
@ -21,6 +23,7 @@ import static net.minestom.server.utils.chunk.ChunkUtils.*;
final class GeneratorImpl {
private static final Vec SECTION_SIZE = new Vec(16);
private static final BiomeManager BIOME_MANAGER = MinecraftServer.getBiomeManager();
static GenerationUnit section(Section section, int sectionX, int sectionY, int sectionZ,
boolean fork) {
@ -217,10 +220,13 @@ final class GeneratorImpl {
@Override
public void setBiome(int x, int y, int z, @NotNull Biome biome) {
if (fork) throw new IllegalStateException("Cannot modify biomes of a fork");
var id = BIOME_MANAGER.getId(biome);
if (id == -1) throw new IllegalStateException("Biome has not been registered: " + biome.namespace());
this.biomePalette.set(
toSectionRelativeCoordinate(x) / 4,
toSectionRelativeCoordinate(y) / 4,
toSectionRelativeCoordinate(z) / 4, biome.id());
toSectionRelativeCoordinate(z) / 4, id);
}
@Override
@ -264,7 +270,9 @@ final class GeneratorImpl {
@Override
public void fillBiome(@NotNull Biome biome) {
if (fork) throw new IllegalStateException("Cannot modify biomes of a fork");
this.biomePalette.fill(biome.id());
var id = MinecraftServer.getBiomeManager().getId(biome);
if (id == -1) throw new IllegalStateException("Biome has not been registered: " + biome.namespace());
this.biomePalette.fill(id);
}
private int retrieveBlockId(Block block) {

View File

@ -3,7 +3,7 @@ package net.minestom.server.instance.block;
import net.minestom.server.coordinate.Point;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.batch.Batch;
import net.minestom.server.registry.ProtocolObject;
import net.minestom.server.registry.StaticProtocolObject;
import net.minestom.server.registry.Registry;
import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagReadable;
@ -23,7 +23,7 @@ import java.util.function.BiPredicate;
* <p>
* Implementations are expected to be immutable.
*/
public sealed interface Block extends ProtocolObject, TagReadable, Blocks permits BlockImpl {
public sealed interface Block extends StaticProtocolObject, TagReadable, Blocks permits BlockImpl {
/**
* Creates a new block with the the property {@code property} sets to {@code value}.

View File

@ -31,7 +31,7 @@ record BlockImpl(@NotNull Registry.BlockEntry registry,
private static final ObjectArray<PropertyType[]> PROPERTIES_TYPE = ObjectArray.singleThread();
// Block id -> Map<PropertiesValues, Block>
private static final ObjectArray<Map<PropertiesHolder, BlockImpl>> POSSIBLE_STATES = ObjectArray.singleThread();
private static final Registry.Container<Block> CONTAINER = Registry.createContainer(Registry.Resource.BLOCKS,
private static final Registry.Container<Block> CONTAINER = Registry.createStaticContainer(Registry.Resource.BLOCKS,
(namespace, properties) -> {
final int blockId = properties.getInt("id");
final Registry.Properties stateObject = properties.section("states");

View File

@ -1,6 +1,6 @@
package net.minestom.server.item;
import net.minestom.server.registry.ProtocolObject;
import net.minestom.server.registry.StaticProtocolObject;
import net.minestom.server.registry.Registry;
import net.minestom.server.utils.NamespaceID;
import org.jetbrains.annotations.Contract;
@ -9,7 +9,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.Collection;
public sealed interface Enchantment extends ProtocolObject, Enchantments permits EnchantmentImpl {
public sealed interface Enchantment extends StaticProtocolObject, Enchantments permits EnchantmentImpl {
/**
* Returns the enchantment registry.

View File

@ -6,7 +6,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.Collection;
record EnchantmentImpl(Registry.EnchantmentEntry registry) implements Enchantment {
private static final Registry.Container<Enchantment> CONTAINER = Registry.createContainer(Registry.Resource.ENCHANTMENTS,
private static final Registry.Container<Enchantment> CONTAINER = Registry.createStaticContainer(Registry.Resource.ENCHANTMENTS,
(namespace, properties) -> new EnchantmentImpl(Registry.enchantment(namespace, properties)));
static Enchantment get(@NotNull String namespace) {

View File

@ -4,7 +4,7 @@ import net.kyori.adventure.text.Component;
import net.minestom.server.instance.block.Block;
import net.minestom.server.item.attribute.ItemAttribute;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.registry.ProtocolObject;
import net.minestom.server.registry.StaticProtocolObject;
import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagReadable;
import net.minestom.server.tag.Taggable;
@ -165,7 +165,7 @@ public sealed interface ItemMeta extends TagReadable, NetworkBuffer.Writer
@Contract("_ -> this")
default @NotNull Builder canPlaceOn(@NotNull Set<@NotNull Block> blocks) {
return set(ItemTags.CAN_PLACE_ON, blocks.stream().map(ProtocolObject::name).toList());
return set(ItemTags.CAN_PLACE_ON, blocks.stream().map(StaticProtocolObject::name).toList());
}
@Contract("_ -> this")
@ -175,7 +175,7 @@ public sealed interface ItemMeta extends TagReadable, NetworkBuffer.Writer
@Contract("_ -> this")
default @NotNull Builder canDestroy(@NotNull Set<@NotNull Block> blocks) {
return set(ItemTags.CAN_DESTROY, blocks.stream().map(ProtocolObject::name).toList());
return set(ItemTags.CAN_DESTROY, blocks.stream().map(StaticProtocolObject::name).toList());
}
@Contract("_ -> this")

View File

@ -1,7 +1,7 @@
package net.minestom.server.item;
import net.minestom.server.instance.block.Block;
import net.minestom.server.registry.ProtocolObject;
import net.minestom.server.registry.StaticProtocolObject;
import net.minestom.server.registry.Registry;
import net.minestom.server.utils.NamespaceID;
import org.jetbrains.annotations.Contract;
@ -10,7 +10,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.Collection;
public sealed interface Material extends ProtocolObject, Materials permits MaterialImpl {
public sealed interface Material extends StaticProtocolObject, Materials permits MaterialImpl {
/**
* Returns the material registry.

View File

@ -6,7 +6,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.Collection;
record MaterialImpl(Registry.MaterialEntry registry) implements Material {
private static final Registry.Container<Material> CONTAINER = Registry.createContainer(Registry.Resource.ITEMS,
private static final Registry.Container<Material> CONTAINER = Registry.createStaticContainer(Registry.Resource.ITEMS,
(namespace, properties) -> new MaterialImpl(Registry.material(namespace, properties)));
static Material get(@NotNull String namespace) {

View File

@ -2,7 +2,7 @@ package net.minestom.server.item.armor;
import net.kyori.adventure.text.Component;
import net.minestom.server.item.Material;
import net.minestom.server.registry.ProtocolObject;
import net.minestom.server.registry.StaticProtocolObject;
import net.minestom.server.registry.Registry;
import net.minestom.server.utils.NamespaceID;
import org.jetbrains.annotations.Contract;
@ -12,7 +12,7 @@ import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import java.util.Collection;
import java.util.Map;
public interface TrimMaterial extends ProtocolObject {
public interface TrimMaterial extends StaticProtocolObject {
static @NotNull TrimMaterial create(@NotNull NamespaceID namespace,
@NotNull String assetName,
@NotNull Material ingredient,

View File

@ -15,7 +15,7 @@ record TrimMaterialImpl(Registry.TrimMaterialEntry registry, int id) implements
private static final Registry.Container<TrimMaterial> CONTAINER;
static {
CONTAINER = Registry.createContainer(Registry.Resource.TRIM_MATERIALS,
CONTAINER = Registry.createStaticContainer(Registry.Resource.TRIM_MATERIALS,
(namespace, properties) -> new TrimMaterialImpl(Registry.trimMaterial(namespace, properties)));
}

View File

@ -2,7 +2,7 @@ package net.minestom.server.item.armor;
import net.kyori.adventure.text.Component;
import net.minestom.server.item.Material;
import net.minestom.server.registry.ProtocolObject;
import net.minestom.server.registry.StaticProtocolObject;
import net.minestom.server.registry.Registry;
import net.minestom.server.utils.NamespaceID;
import org.jetbrains.annotations.Contract;
@ -11,7 +11,7 @@ import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import java.util.Collection;
public interface TrimPattern extends ProtocolObject {
public interface TrimPattern extends StaticProtocolObject {
static @NotNull TrimPattern create(@NotNull NamespaceID namespace,
@NotNull NamespaceID assetID,
@NotNull Material template,

View File

@ -13,7 +13,7 @@ record TrimPatternImpl(Registry.TrimPatternEntry registry, int id) implements Tr
private static final Registry.Container<TrimPattern> CONTAINER;
static {
CONTAINER = Registry.createContainer(Registry.Resource.TRIM_PATTERNS,
CONTAINER = Registry.createStaticContainer(Registry.Resource.TRIM_PATTERNS,
(namespace, properties) -> new TrimPatternImpl(Registry.trimPattern(namespace, properties)));
}

View File

@ -4,7 +4,7 @@ import net.minestom.server.color.Color;
import net.minestom.server.item.ItemMetaView;
import net.minestom.server.potion.CustomPotionEffect;
import net.minestom.server.potion.PotionType;
import net.minestom.server.registry.ProtocolObject;
import net.minestom.server.registry.StaticProtocolObject;
import net.minestom.server.tag.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -13,7 +13,7 @@ import org.jetbrains.annotations.UnknownNullability;
import java.util.List;
public record PotionMeta(TagReadable readable) implements ItemMetaView<PotionMeta.Builder> {
private static final Tag<PotionType> POTION_TYPE = Tag.String("Potion").map(PotionType::fromNamespaceId, ProtocolObject::name).defaultValue(PotionType.EMPTY);
private static final Tag<PotionType> POTION_TYPE = Tag.String("Potion").map(PotionType::fromNamespaceId, StaticProtocolObject::name).defaultValue(PotionType.EMPTY);
private static final Tag<List<CustomPotionEffect>> CUSTOM_POTION_EFFECTS = Tag.Structure("CustomPotionEffects", new TagSerializer<CustomPotionEffect>() {
@Override
public @Nullable CustomPotionEffect read(@NotNull TagReadable reader) {

View File

@ -5,7 +5,7 @@ import net.minestom.server.network.ConnectionState;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.packet.server.ServerPacket;
import net.minestom.server.network.packet.server.ServerPacketIdentifier;
import net.minestom.server.registry.ProtocolObject;
import net.minestom.server.registry.StaticProtocolObject;
import net.minestom.server.utils.PacketUtils;
import org.jetbrains.annotations.NotNull;
@ -93,7 +93,7 @@ public record DeclareCommandsPacket(@NotNull List<Node> nodes,
}
if (isArgument()) {
final ProtocolObject object = Argument.CONTAINER.getId(reader.read(VAR_INT));
final StaticProtocolObject object = Argument.CONTAINER.getId(reader.read(VAR_INT));
parser = object.name();
properties = getProperties(reader, parser);
}

View File

@ -1,13 +1,13 @@
package net.minestom.server.particle;
import net.minestom.server.registry.ProtocolObject;
import net.minestom.server.registry.StaticProtocolObject;
import net.minestom.server.utils.NamespaceID;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
public sealed interface Particle extends ProtocolObject, Particles permits ParticleImpl {
public sealed interface Particle extends StaticProtocolObject, Particles permits ParticleImpl {
static @NotNull Collection<@NotNull Particle> values() {
return ParticleImpl.values();

View File

@ -7,7 +7,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.Collection;
record ParticleImpl(NamespaceID namespace, int id) implements Particle {
private static final Registry.Container<Particle> CONTAINER = Registry.createContainer(Registry.Resource.PARTICLES,
private static final Registry.Container<Particle> CONTAINER = Registry.createStaticContainer(Registry.Resource.PARTICLES,
(namespace, properties) -> new ParticleImpl(NamespaceID.from(namespace), properties.getInt("id")));
static Particle get(@NotNull String namespace) {

View File

@ -1,6 +1,6 @@
package net.minestom.server.potion;
import net.minestom.server.registry.ProtocolObject;
import net.minestom.server.registry.StaticProtocolObject;
import net.minestom.server.registry.Registry;
import net.minestom.server.utils.NamespaceID;
import org.jetbrains.annotations.Contract;
@ -9,7 +9,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.Collection;
public sealed interface PotionEffect extends ProtocolObject, PotionEffects permits PotionEffectImpl {
public sealed interface PotionEffect extends StaticProtocolObject, PotionEffects permits PotionEffectImpl {
@Contract(pure = true)
@NotNull Registry.PotionEffectEntry registry();

View File

@ -6,7 +6,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.Collection;
record PotionEffectImpl(Registry.PotionEffectEntry registry) implements PotionEffect {
private static final Registry.Container<PotionEffect> CONTAINER = Registry.createContainer(Registry.Resource.POTION_EFFECTS,
private static final Registry.Container<PotionEffect> CONTAINER = Registry.createStaticContainer(Registry.Resource.POTION_EFFECTS,
(namespace, properties) -> new PotionEffectImpl(Registry.potionEffect(namespace, properties)));
static PotionEffect get(@NotNull String namespace) {

View File

@ -1,13 +1,13 @@
package net.minestom.server.potion;
import net.minestom.server.registry.ProtocolObject;
import net.minestom.server.registry.StaticProtocolObject;
import net.minestom.server.utils.NamespaceID;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
public sealed interface PotionType extends ProtocolObject, PotionTypes permits PotionTypeImpl {
public sealed interface PotionType extends StaticProtocolObject, PotionTypes permits PotionTypeImpl {
static @NotNull Collection<@NotNull PotionType> values() {
return PotionTypeImpl.values();

View File

@ -7,7 +7,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.Collection;
record PotionTypeImpl(NamespaceID namespace, int id) implements PotionType {
private static final Registry.Container<PotionType> CONTAINER = Registry.createContainer(Registry.Resource.POTION_TYPES,
private static final Registry.Container<PotionType> CONTAINER = Registry.createStaticContainer(Registry.Resource.POTION_TYPES,
(namespace, properties) -> new PotionTypeImpl(NamespaceID.from(namespace), properties.getInt("id")));
static PotionType get(@NotNull String namespace) {

View File

@ -11,17 +11,14 @@ public interface ProtocolObject extends Keyed {
@Contract(pure = true)
@NotNull NamespaceID namespace();
@Override
@Contract(pure = true)
default @NotNull Key key() {
return namespace();
}
@Contract(pure = true)
default @NotNull String name() {
return namespace().asString();
}
@Override
@Contract(pure = true)
int id();
default @NotNull Key key() {
return namespace();
}
}

View File

@ -28,7 +28,7 @@ import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
* Handles registry data, used by {@link ProtocolObject} implementations and is strictly internal.
* Handles registry data, used by {@link StaticProtocolObject} implementations and is strictly internal.
* Use at your own risk.
*/
public final class Registry {
@ -37,6 +37,11 @@ public final class Registry {
return new BlockEntry(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);
@ -89,7 +94,7 @@ public final class Registry {
}
@ApiStatus.Internal
public static <T extends ProtocolObject> Container<T> createContainer(Resource resource, Container.Loader<T> loader) {
public static <T extends StaticProtocolObject> Container<T> createStaticContainer(Resource resource, Container.Loader<T> loader) {
var entries = Registry.load(resource);
Map<String, T> namespaces = new HashMap<>(entries.size());
ObjectArray<T> ids = ObjectArray.singleThread(entries.size());
@ -104,9 +109,9 @@ public final class Registry {
}
@ApiStatus.Internal
public record Container<T extends ProtocolObject>(Resource resource,
Map<String, T> namespaces,
ObjectArray<T> ids) {
public record Container<T extends StaticProtocolObject>(Resource resource,
Map<String, T> namespaces,
ObjectArray<T> ids) {
public Container {
namespaces = Map.copyOf(namespaces);
ids.trim();
@ -149,6 +154,54 @@ public final class Registry {
}
}
@ApiStatus.Internal
public static <T extends ProtocolObject> DynamicContainer<T> createDynamicContainer(Resource resource, Container.Loader<T> loader) {
var entries = Registry.load(resource);
Map<String, T> namespaces = new HashMap<>(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);
namespaces.put(value.name(), value);
}
return new DynamicContainer<>(resource, namespaces);
}
@ApiStatus.Internal
public record DynamicContainer<T>(Resource resource, Map<String, T> namespaces) {
public DynamicContainer {
namespaces = Map.copyOf(namespaces);
}
public T get(@NotNull String namespace) {
return namespaces.get(namespace);
}
public T getSafe(@NotNull String namespace) {
return get(namespace.contains(":") ? namespace : "minecraft:" + namespace);
}
public Collection<T> 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 extends ProtocolObject> {
T get(String namespace, Properties properties);
}
}
@ApiStatus.Internal
public enum Resource {
BLOCKS("blocks.json"),
@ -168,7 +221,8 @@ public final class Registry {
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");
ITEM_TAGS("tags/item_tags.json"),
BIOMES("biomes.json");
private final String name;
@ -323,6 +377,81 @@ public final class Registry {
}
}
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 int id;
@ -600,6 +729,11 @@ public final class Registry {
return new PropertiesMap(map);
}
@Override
public boolean containsKey(String name) {
return map.containsKey(name);
}
@Override
public Map<String, Object> asMap() {
return map;
@ -642,6 +776,8 @@ public final class Registry {
Properties section(String name);
boolean containsKey(String name);
Map<String, Object> asMap();
@Override

View File

@ -0,0 +1,9 @@
package net.minestom.server.registry;
import org.jetbrains.annotations.Contract;
public interface StaticProtocolObject extends ProtocolObject {
@Contract(pure = true)
int id();
}

View File

@ -2,14 +2,14 @@ package net.minestom.server.sound;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.sound.Sound;
import net.minestom.server.registry.ProtocolObject;
import net.minestom.server.registry.StaticProtocolObject;
import net.minestom.server.utils.NamespaceID;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
public sealed interface SoundEvent extends ProtocolObject, Sound.Type, SoundEvents permits SoundEventImpl {
public sealed interface SoundEvent extends StaticProtocolObject, Sound.Type, SoundEvents permits SoundEventImpl {
static @NotNull Collection<@NotNull SoundEvent> values() {
return SoundEventImpl.values();
@ -29,6 +29,6 @@ public sealed interface SoundEvent extends ProtocolObject, Sound.Type, SoundEven
@Override
default @NotNull Key key() {
return ProtocolObject.super.key();
return StaticProtocolObject.super.key();
}
}

View File

@ -7,7 +7,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.Collection;
record SoundEventImpl(NamespaceID namespace, int id) implements SoundEvent {
private static final Registry.Container<SoundEvent> CONTAINER = Registry.createContainer(Registry.Resource.SOUNDS,
private static final Registry.Container<SoundEvent> CONTAINER = Registry.createStaticContainer(Registry.Resource.SOUNDS,
(namespace, properties) -> new SoundEventImpl(NamespaceID.from(namespace), properties.getInt("id")));
static SoundEvent get(@NotNull String namespace) {

View File

@ -1,13 +1,13 @@
package net.minestom.server.statistic;
import net.minestom.server.registry.ProtocolObject;
import net.minestom.server.registry.StaticProtocolObject;
import net.minestom.server.utils.NamespaceID;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
public sealed interface StatisticType extends ProtocolObject, StatisticTypes permits StatisticTypeImpl {
public sealed interface StatisticType extends StaticProtocolObject, StatisticTypes permits StatisticTypeImpl {
static @NotNull Collection<@NotNull StatisticType> values() {
return StatisticTypeImpl.values();

View File

@ -7,7 +7,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.Collection;
record StatisticTypeImpl(NamespaceID namespace, int id) implements StatisticType {
private static final Registry.Container<StatisticType> CONTAINER = Registry.createContainer(Registry.Resource.STATISTICS,
private static final Registry.Container<StatisticType> CONTAINER = Registry.createStaticContainer(Registry.Resource.STATISTICS,
(namespace, properties) -> new StatisticTypeImpl(NamespaceID.from(namespace), properties.getInt("id")));
static StatisticType get(@NotNull String namespace) {

View File

@ -1,206 +1,46 @@
package net.minestom.server.world.biomes;
import net.minestom.server.coordinate.Point;
import net.minestom.server.registry.ProtocolObject;
import net.minestom.server.registry.Registry;
import net.minestom.server.utils.NamespaceID;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.Contract;
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.Locale;
import java.util.concurrent.atomic.AtomicInteger;
public final class Biome {
public static final AtomicInteger ID_COUNTER = new AtomicInteger(0);
private static final BiomeEffects DEFAULT_EFFECTS = BiomeEffects.builder()
.fogColor(0xC0D8FF)
.skyColor(0x78A7FF)
.waterColor(0x3F76E4)
.waterFogColor(0x50533)
.build();
sealed public interface Biome extends ProtocolObject permits BiomeImpl {
/**
* Returns the entity registry.
*
* @return the entity registry or null if it was created with a builder
*/
@Contract(pure = true)
@Nullable Registry.BiomeEntry registry();
//A plains biome has to be registered or else minecraft will crash
public static final Biome PLAINS = Biome.builder()
.category(Category.NONE)
.name(NamespaceID.from("minecraft:plains"))
.temperature(0.8F)
.downfall(0.4F)
.depth(0.125F)
.scale(0.05F)
.effects(DEFAULT_EFFECTS)
.build();
@Override
@NotNull NamespaceID namespace();
float depth();
float temperature();
float scale();
float downfall();
@NotNull BiomeEffects effects();
@NotNull Precipitation precipitation();
@NotNull TemperatureModifier temperatureModifier();
private final int id = ID_COUNTER.getAndIncrement();
private final NamespaceID name;
private final float depth;
private final float temperature;
private final float scale;
private final float downfall;
private final Category category;
private final BiomeEffects effects;
private final Precipitation precipitation;
private final TemperatureModifier temperatureModifier;
Biome(NamespaceID name, float depth, float temperature, float scale, float downfall, Category category, BiomeEffects effects, Precipitation precipitation, TemperatureModifier temperatureModifier) {
this.name = name;
this.depth = depth;
this.temperature = temperature;
this.scale = scale;
this.downfall = downfall;
this.category = category;
this.effects = effects;
this.precipitation = precipitation;
this.temperatureModifier = temperatureModifier;
}
public static Builder builder() {
return new Builder();
}
public @NotNull NBTCompound toNbt() {
Check.notNull(name, "The biome namespace cannot be null");
Check.notNull(effects, "The biome effects cannot be null");
return NBT.Compound(nbt -> {
nbt.setString("name", name.toString());
nbt.setInt("id", id());
nbt.set("element", NBT.Compound(element -> {
element.setFloat("depth", depth);
element.setFloat("temperature", temperature);
element.setFloat("scale", scale);
element.setFloat("downfall", downfall);
element.setString("category", category.name().toLowerCase(Locale.ROOT));
element.setByte("has_precipitation", (byte) (precipitation == Precipitation.NONE ? 0 : 1));
element.setString("precipitation", precipitation.name().toLowerCase(Locale.ROOT));
if (temperatureModifier != TemperatureModifier.NONE)
element.setString("temperature_modifier", temperatureModifier.name().toLowerCase(Locale.ROOT));
element.set("effects", effects.toNbt());
}));
});
}
public int id() {
return this.id;
}
public NamespaceID name() {
return this.name;
}
public float depth() {
return this.depth;
}
public float temperature() {
return this.temperature;
}
public float scale() {
return this.scale;
}
public float downfall() {
return this.downfall;
}
public Category category() {
return this.category;
}
public BiomeEffects effects() {
return this.effects;
}
public Precipitation precipitation() {
return this.precipitation;
}
public TemperatureModifier temperatureModifier() {
return this.temperatureModifier;
}
public enum Precipitation {
enum Precipitation {
NONE, RAIN, SNOW;
}
public enum Category {
NONE, TAIGA, EXTREME_HILLS, JUNGLE, MESA, PLAINS,
SAVANNA, ICY, THE_END, BEACH, FOREST, OCEAN,
DESERT, RIVER, SWAMP, MUSHROOM, NETHER, UNDERGROUND,
MOUNTAIN;
}
public enum TemperatureModifier {
enum TemperatureModifier {
NONE, FROZEN;
}
public static final class Builder {
private NamespaceID name;
private float depth = 0.2f;
private float temperature = 0.25f;
private float scale = 0.2f;
private float downfall = 0.8f;
private Category category = Category.NONE;
private BiomeEffects effects = DEFAULT_EFFECTS;
private Precipitation precipitation = Precipitation.RAIN;
private TemperatureModifier temperatureModifier = TemperatureModifier.NONE;
Builder() {
}
public Builder name(NamespaceID name) {
this.name = name;
return this;
}
public Builder depth(float depth) {
this.depth = depth;
return this;
}
public Builder temperature(float temperature) {
this.temperature = temperature;
return this;
}
public Builder scale(float scale) {
this.scale = scale;
return this;
}
public Builder downfall(float downfall) {
this.downfall = downfall;
return this;
}
public Builder category(Category category) {
this.category = category;
return this;
}
public Builder effects(BiomeEffects effects) {
this.effects = effects;
return this;
}
public Builder precipitation(Precipitation precipitation) {
this.precipitation = precipitation;
return this;
}
public Builder temperatureModifier(TemperatureModifier temperatureModifier) {
this.temperatureModifier = temperatureModifier;
return this;
}
public Biome build() {
return new Biome(name, depth, temperature, scale, downfall, category, effects, precipitation, temperatureModifier);
}
}
public interface Setter {
interface Setter {
void setBiome(int x, int y, int z, @NotNull Biome biome);
default void setBiome(@NotNull Point blockPosition, @NotNull Biome biome) {
@ -208,11 +48,103 @@ public final class Biome {
}
}
public interface Getter {
interface Getter {
@NotNull Biome getBiome(int x, int y, int z);
default @NotNull Biome getBiome(@NotNull Point point) {
return getBiome(point.blockX(), point.blockY(), point.blockZ());
}
}
default @NotNull NBTCompound toNbt() {
Check.notNull(name(), "The biome namespace cannot be null");
Check.notNull(effects(), "The biome effects cannot be null");
return NBT.Compound(element -> {
element.setFloat("depth", depth());
element.setFloat("temperature", temperature());
element.setFloat("scale", scale());
element.setFloat("downfall", downfall());
element.setByte("has_precipitation", (byte) (precipitation() == Precipitation.NONE ? 0 : 1));
element.setString("precipitation", precipitation().name().toLowerCase(Locale.ROOT));
if (temperatureModifier() != TemperatureModifier.NONE)
element.setString("temperature_modifier", temperatureModifier().name().toLowerCase(Locale.ROOT));
element.set("effects", effects().toNbt());
});
}
static @NotNull Builder builder() {
return new Builder();
}
final class Builder {
private static final BiomeEffects DEFAULT_EFFECTS = BiomeEffects.builder()
.fogColor(0xC0D8FF)
.skyColor(0x78A7FF)
.waterColor(0x3F76E4)
.waterFogColor(0x50533)
.build();
private NamespaceID name;
private float depth = 0.2f;
private float temperature = 0.25f;
private float scale = 0.2f;
private float downfall = 0.8f;
private BiomeEffects effects = DEFAULT_EFFECTS;
private Precipitation precipitation = Precipitation.RAIN;
private TemperatureModifier temperatureModifier = TemperatureModifier.NONE;
@Contract(value = "_ -> this", pure = true)
public @NotNull Builder name(@NotNull NamespaceID name) {
this.name = name;
return this;
}
@Contract(value = "_ -> this", pure = true)
public @NotNull Builder depth(float depth) {
this.depth = depth;
return this;
}
@Contract(value = "_ -> this", pure = true)
public @NotNull Builder temperature(float temperature) {
this.temperature = temperature;
return this;
}
@Contract(value = "_ -> this", pure = true)
public @NotNull Builder scale(float scale) {
this.scale = scale;
return this;
}
@Contract(value = "_ -> this", pure = true)
public @NotNull Builder downfall(float downfall) {
this.downfall = downfall;
return this;
}
@Contract(value = "_ -> this", pure = true)
public @NotNull Builder effects(@NotNull BiomeEffects effects) {
this.effects = effects;
return this;
}
@Contract(value = "_ -> this", pure = true)
public @NotNull Builder precipitation(@NotNull Biome.Precipitation precipitation) {
this.precipitation = precipitation;
return this;
}
@Contract(value = "_ -> this", pure = true)
public @NotNull Builder temperatureModifier(@NotNull TemperatureModifier temperatureModifier) {
this.temperatureModifier = temperatureModifier;
return this;
}
@Contract(pure = true)
public @NotNull Biome build() {
return new BiomeImpl(name, depth, temperature, scale, downfall, effects, precipitation, temperatureModifier);
}
}
}

View File

@ -0,0 +1,123 @@
package net.minestom.server.world.biomes;
import net.minestom.server.registry.ProtocolObject;
import net.minestom.server.registry.Registry;
import net.minestom.server.utils.NamespaceID;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
final class BiomeImpl implements ProtocolObject, Biome {
// https://minecraft.wiki/w/Rain
private final static Double SNOW_TEMPERATURE = 0.15;
private static final Registry.DynamicContainer<BiomeImpl> CONTAINER = Registry.createDynamicContainer(Registry.Resource.BIOMES,
(namespace, properties) -> new BiomeImpl(Registry.biome(namespace, properties)));
static Collection<BiomeImpl> values() {
return CONTAINER.values();
}
static BiomeImpl get(@NotNull String namespace) {
return CONTAINER.get(namespace);
}
static BiomeImpl getSafe(@NotNull String namespace) {
return CONTAINER.getSafe(namespace);
}
@NotNull
private final NamespaceID name;
private final float depth;
private final float temperature;
private final float scale;
private final float downfall;
@NotNull
private final BiomeEffects effects;
@NotNull
private final Precipitation precipitation;
@NotNull
private final TemperatureModifier temperatureModifier;
BiomeImpl(NamespaceID name, float depth, float temperature, float scale, float downfall, BiomeEffects effects, Precipitation precipitation, TemperatureModifier temperatureModifier) {
this.name = name;
this.depth = depth;
this.temperature = temperature;
this.scale = scale;
this.downfall = downfall;
this.effects = effects;
this.precipitation = precipitation;
this.temperatureModifier = temperatureModifier;
}
BiomeImpl(Registry.BiomeEntry entry) {
this.name = entry.namespace();
this.depth = 0.2f;
this.scale = 0.2f;
this.temperature = entry.temperature();
BiomeEffects.Builder effectsBuilder = getBuilder(entry);
this.effects = effectsBuilder.build();
this.precipitation = entry.hasPrecipitation()
? temperature < SNOW_TEMPERATURE
? Biome.Precipitation.SNOW
: Biome.Precipitation.RAIN
: Biome.Precipitation.NONE;
this.downfall = entry.downfall();
this.temperatureModifier = entry.temperature() < SNOW_TEMPERATURE ? TemperatureModifier.FROZEN : TemperatureModifier.NONE;
}
@NotNull
private static BiomeEffects.Builder getBuilder(Registry.BiomeEntry entry) {
BiomeEffects.Builder effectsBuilder = BiomeEffects.builder();
if (entry.foliageColor() != null) effectsBuilder.foliageColor(entry.foliageColor());
if (entry.grassColor() != null) effectsBuilder.grassColor(entry.grassColor());
if (entry.skyColor() != null) effectsBuilder.skyColor(entry.skyColor());
if (entry.waterColor() != null) effectsBuilder.waterColor(entry.waterColor());
if (entry.waterFogColor() != null) effectsBuilder.waterFogColor(entry.waterFogColor());
if (entry.fogColor() != null) effectsBuilder.fogColor(entry.fogColor());
return effectsBuilder;
}
@Nullable
@Override
public Registry.BiomeEntry registry() {
return null;
}
@Override
public @NotNull NamespaceID namespace() {
return this.name;
}
public float depth() {
return this.depth;
}
public float temperature() {
return this.temperature;
}
public float scale() {
return this.scale;
}
public float downfall() {
return this.downfall;
}
public @NotNull BiomeEffects effects() {
return this.effects;
}
public @NotNull Precipitation precipitation() {
return this.precipitation;
}
public @NotNull TemperatureModifier temperatureModifier() {
return this.temperatureModifier;
}
}

View File

@ -1,6 +1,9 @@
package net.minestom.server.world.biomes;
import net.minestom.server.utils.NamespaceID;
import net.minestom.server.utils.validate.Check;
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.NBTType;
@ -9,18 +12,32 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Allows servers to register custom dimensions. Also used during player joining to send the list of all existing dimensions.
* <p>
* Contains {@link Biome#PLAINS} by default but can be removed.
*/
public final class BiomeManager {
private final Map<Integer, Biome> biomes = new ConcurrentHashMap<>();
private final Map<NamespaceID, Biome> biomesByName = new ConcurrentHashMap<>();
private final Map<NamespaceID, Integer> idMappings = new ConcurrentHashMap<>();
private final AtomicInteger ID_COUNTER = new AtomicInteger(0);
private NBTCompound nbtCache = null;
public BiomeManager() {
addBiome(Biome.PLAINS);
// Need to register plains for the client to work properly
// Plains is always ID 0
addBiome(BiomeImpl.get("minecraft:plains"));
}
public void loadVanillaBiomes() {
for (BiomeImpl biome : BiomeImpl.values()) {
if (getByName(biome.namespace()) == null)
addBiome(biome);
}
}
/**
@ -28,8 +45,14 @@ public final class BiomeManager {
*
* @param biome the biome to add
*/
public void addBiome(Biome biome) {
this.biomes.put(biome.id(), biome);
public void addBiome(@NotNull Biome biome) {
Check.stateCondition(getByName(biome.namespace()) != null, "The biome " + biome.namespace() + " has already been registered");
var id = ID_COUNTER.getAndIncrement();
this.biomes.put(id, biome);
this.biomesByName.put(biome.namespace(), biome);
this.idMappings.put(biome.namespace(), id);
nbtCache = null;
}
/**
@ -37,8 +60,14 @@ public final class BiomeManager {
*
* @param biome the biome to remove
*/
public void removeBiome(Biome biome) {
this.biomes.remove(biome.id());
public void removeBiome(@NotNull Biome biome) {
var id = idMappings.get(biome.namespace());
if (id != null) {
biomes.remove(id);
biomesByName.remove(biome.namespace());
idMappings.remove(biome.namespace());
nbtCache = null;
}
}
/**
@ -56,24 +85,44 @@ public final class BiomeManager {
* @param id the id of the biome
* @return the {@link Biome} linked to this id
*/
@Nullable
public Biome getById(int id) {
return biomes.get(id);
}
public Biome getByName(NamespaceID namespaceID) {
Biome biome = null;
for (final Biome biomeT : biomes.values()) {
if (biomeT.name().equals(namespaceID)) {
biome = biomeT;
break;
}
}
return biome;
@Nullable
public Biome getByName(@NotNull NamespaceID namespaceID) {
return biomesByName.get(namespaceID);
}
public NBTCompound toNBT() {
return NBT.Compound(Map.of(
@Nullable
public Biome getByName(@NotNull String namespaceID) {
NamespaceID namespace = NamespaceID.from(namespaceID);
return getByName(namespace);
}
public @NotNull NBTCompound toNBT() {
if (nbtCache != null) return nbtCache;
nbtCache = NBT.Compound(Map.of(
"type", NBT.String("minecraft:worldgen/biome"),
"value", NBT.List(NBTType.TAG_Compound, biomes.values().stream().map(Biome::toNbt).toList())));
"value", NBT.List(NBTType.TAG_Compound, biomes.values().stream().map(biome -> {
return NBT.Compound(Map.of(
"id", NBT.Int(getId(biome)),
"name", NBT.String(biome.namespace().toString()),
"element", biome.toNbt()
));
}).toList())));
return nbtCache;
}
/**
* Gets the id of a biome.
*`
* @param biome
* @return the id of the biome, or -1 if the biome is not registered
*/
public int getId(Biome biome) {
return idMappings.getOrDefault(biome.namespace(), -1);
}
}

View File

@ -0,0 +1,4 @@
package net.minestom.server.world.biomes;
final public class VanillaBiome implements Biomes {
}

View File

@ -70,13 +70,11 @@ public class AnvilLoaderIntegrationTest {
assertEquals(-4, chunk.getMinSection());
assertEquals(20, chunk.getMaxSection());
// TODO: skylight
// TODO: block light
for (int y = 0; y < 16; y++) {
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
Biome b = chunk.getBiome(x, y, z);
assertEquals(NamespaceID.from("minecraft:plains"), b.name());
assertEquals(NamespaceID.from("minecraft:plains"), b.namespace());
}
}
}

View File

@ -0,0 +1,72 @@
package net.minestom.server.instance;
import net.minestom.server.MinecraftServer;
import net.minestom.server.instance.generator.Generator;
import net.minestom.server.utils.NamespaceID;
import net.minestom.server.world.biomes.Biome;
import net.minestom.testing.Env;
import net.minestom.testing.EnvTest;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
@EnvTest
public class BiomeIntegrationTest {
private static Biome PLAINS;
private static int PLAINS_ID;
@BeforeAll
public static void prepareTest(Env env) {
PLAINS = MinecraftServer.getBiomeManager().getByName(NamespaceID.from("minecraft:plains"));
PLAINS_ID = MinecraftServer.getBiomeManager().getId(PLAINS);
}
@Test
public void chunkBiomeSet(Env env) {
final int minSection = -1;
final int maxSection = 5;
final int chunkX = 3;
final int chunkZ = -2;
final int sectionCount = maxSection - minSection;
Section[] sections = new Section[sectionCount];
Arrays.setAll(sections, i -> new Section());
var chunkUnits = GeneratorImpl.chunk(minSection, maxSection, List.of(sections), chunkX, chunkZ);
Generator generator = chunk -> {
var modifier = chunk.modifier();
modifier.setBiome(48, 0, -32, PLAINS);
modifier.setBiome(48 + 8, 0, -32, PLAINS);
};
generator.generate(chunkUnits);
assertEquals(PLAINS_ID, sections[0].biomePalette().get(0, 0, 0));
assertEquals(0, sections[0].biomePalette().get(1, 0, 0));
assertEquals(PLAINS_ID, sections[0].biomePalette().get(2, 0, 0));
}
@Test
public void chunkBiomeFill(Env env) {
final int minSection = -1;
final int maxSection = 5;
final int chunkX = 3;
final int chunkZ = -2;
final int sectionCount = maxSection - minSection;
Section[] sections = new Section[sectionCount];
Arrays.setAll(sections, i -> new Section());
var chunkUnits = GeneratorImpl.chunk(minSection, maxSection, List.of(sections), chunkX, chunkZ);
Generator generator = chunk -> {
var modifier = chunk.modifier();
modifier.fillBiome(PLAINS);
};
generator.generate(chunkUnits);
for (var section : sections) {
section.biomePalette().getAll((x, y, z, value) ->
assertEquals(PLAINS_ID, value));
}
}
}

View File

@ -1,11 +1,12 @@
package net.minestom.server.instance;
import net.minestom.testing.Env;
import net.minestom.testing.EnvTest;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Point;
import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.generator.GenerationUnit;
import net.minestom.server.world.biomes.Biome;
import net.minestom.server.utils.NamespaceID;
import net.minestom.testing.Env;
import net.minestom.testing.EnvTest;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
@ -97,11 +98,13 @@ public class GeneratorForkIntegrationTest {
@Test
public void biome(Env env) {
var manager = env.process().instance();
var plains = MinecraftServer.getBiomeManager().getByName(NamespaceID.from("minecraft:plains"));
var instance = manager.createInstanceContainer();
instance.setGenerator(unit -> {
var u = unit.fork(unit.absoluteStart(), unit.absoluteEnd().add(16, 0, 16));
assertThrows(IllegalStateException.class, () -> u.modifier().setBiome(16, 0, 0, Biome.PLAINS));
assertThrows(IllegalStateException.class, () -> u.modifier().fillBiome(Biome.PLAINS));
assertThrows(IllegalStateException.class, () -> u.modifier().setBiome(16, 0, 0, plains));
assertThrows(IllegalStateException.class, () -> u.modifier().fillBiome(plains));
});
instance.loadChunk(0, 0).join();
}

View File

@ -6,8 +6,10 @@ import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.generator.GenerationUnit;
import net.minestom.server.instance.generator.Generator;
import net.minestom.server.utils.MathUtils;
import net.minestom.server.utils.NamespaceID;
import net.minestom.server.utils.chunk.ChunkUtils;
import net.minestom.server.world.biomes.Biome;
import net.minestom.server.world.biomes.BiomeEffects;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
@ -26,7 +28,6 @@ import static net.minestom.server.utils.chunk.ChunkUtils.floorSection;
import static org.junit.jupiter.api.Assertions.*;
public class GeneratorTest {
@Test
public void unitSize() {
assertDoesNotThrow(() -> dummyUnit(Vec.ZERO, new Vec(16)));
@ -241,48 +242,6 @@ public class GeneratorTest {
}
}
@Test
public void chunkBiomeSet() {
final int minSection = -1;
final int maxSection = 5;
final int chunkX = 3;
final int chunkZ = -2;
final int sectionCount = maxSection - minSection;
Section[] sections = new Section[sectionCount];
Arrays.setAll(sections, i -> new Section());
var chunkUnits = GeneratorImpl.chunk(minSection, maxSection, List.of(sections), chunkX, chunkZ);
Generator generator = chunk -> {
var modifier = chunk.modifier();
modifier.setBiome(48, 0, -32, Biome.PLAINS);
modifier.setBiome(48 + 8, 0, -32, Biome.PLAINS);
};
generator.generate(chunkUnits);
assertEquals(Biome.PLAINS.id(), sections[0].biomePalette().get(0, 0, 0));
assertEquals(0, sections[0].biomePalette().get(1, 0, 0));
assertEquals(Biome.PLAINS.id(), sections[0].biomePalette().get(2, 0, 0));
}
@Test
public void chunkBiomeFill() {
final int minSection = -1;
final int maxSection = 5;
final int chunkX = 3;
final int chunkZ = -2;
final int sectionCount = maxSection - minSection;
Section[] sections = new Section[sectionCount];
Arrays.setAll(sections, i -> new Section());
var chunkUnits = GeneratorImpl.chunk(minSection, maxSection, List.of(sections), chunkX, chunkZ);
Generator generator = chunk -> {
var modifier = chunk.modifier();
modifier.fillBiome(Biome.PLAINS);
};
generator.generate(chunkUnits);
for (var section : sections) {
section.biomePalette().getAll((x, y, z, value) ->
assertEquals(Biome.PLAINS.id(), value));
}
}
@Test
public void chunkFillHeightExact() {
final int minSection = -1;