Introduce BCLChunkGenerator class

This commit is contained in:
Frank 2022-05-22 03:26:03 +02:00
parent ab0895d48c
commit 0a5a608b7d
17 changed files with 84 additions and 73 deletions

View file

@ -11,7 +11,7 @@ import org.betterx.bclib.api.dataexchange.DataExchangeAPI;
import org.betterx.bclib.api.dataexchange.handler.autosync.*; import org.betterx.bclib.api.dataexchange.handler.autosync.*;
import org.betterx.bclib.api.tag.TagAPI; import org.betterx.bclib.api.tag.TagAPI;
import org.betterx.bclib.config.Configs; import org.betterx.bclib.config.Configs;
import org.betterx.bclib.presets.WorldPresets; import org.betterx.bclib.presets.worldgen.WorldPresets;
import org.betterx.bclib.recipes.AnvilRecipe; import org.betterx.bclib.recipes.AnvilRecipe;
import org.betterx.bclib.recipes.CraftingRecipes; import org.betterx.bclib.recipes.CraftingRecipes;
import org.betterx.bclib.registry.BaseBlockEntities; import org.betterx.bclib.registry.BaseBlockEntities;

View file

@ -124,11 +124,11 @@ public class BiomeAPI {
public static final BCLBiome THE_END = registerEndLandBiome(getFromRegistry(Biomes.THE_END)); public static final BCLBiome THE_END = registerEndLandBiome(getFromRegistry(Biomes.THE_END));
public static final BCLBiome END_MIDLANDS = registerSubBiome(THE_END, public static final BCLBiome END_MIDLANDS = registerSubBiome(THE_END,
getFromRegistry(Biomes.END_MIDLANDS).value(), getFromRegistry(Biomes.END_MIDLANDS).value(),
0.5F); 0.5F);
public static final BCLBiome END_HIGHLANDS = registerSubBiome(THE_END, public static final BCLBiome END_HIGHLANDS = registerSubBiome(THE_END,
getFromRegistry(Biomes.END_HIGHLANDS).value(), getFromRegistry(Biomes.END_HIGHLANDS).value(),
0.5F); 0.5F);
public static final BCLBiome END_BARRENS = registerEndVoidBiome(getFromRegistry(new ResourceLocation("end_barrens"))); public static final BCLBiome END_BARRENS = registerEndVoidBiome(getFromRegistry(new ResourceLocation("end_barrens")));
public static final BCLBiome SMALL_END_ISLANDS = registerEndVoidBiome(getFromRegistry(new ResourceLocation( public static final BCLBiome SMALL_END_ISLANDS = registerEndVoidBiome(getFromRegistry(new ResourceLocation(
@ -289,7 +289,7 @@ public class BiomeAPI {
*/ */
public static BCLBiome registerEndLandBiome(Holder<Biome> biome, float genChance) { public static BCLBiome registerEndLandBiome(Holder<Biome> biome, float genChance) {
BCLBiome bclBiome = new BCLBiome(biome.value(), BCLBiome bclBiome = new BCLBiome(biome.value(),
VanillaBiomeSettings.createVanilla().setGenChance(genChance).build()); VanillaBiomeSettings.createVanilla().setGenChance(genChance).build());
registerBiome(bclBiome, Dimension.END_LAND); registerBiome(bclBiome, Dimension.END_LAND);
return bclBiome; return bclBiome;
@ -335,7 +335,7 @@ public class BiomeAPI {
*/ */
public static BCLBiome registerEndVoidBiome(Holder<Biome> biome, float genChance) { public static BCLBiome registerEndVoidBiome(Holder<Biome> biome, float genChance) {
BCLBiome bclBiome = new BCLBiome(biome.value(), BCLBiome bclBiome = new BCLBiome(biome.value(),
VanillaBiomeSettings.createVanilla().setGenChance(genChance).build()); VanillaBiomeSettings.createVanilla().setGenChance(genChance).build());
registerBiome(bclBiome, Dimension.END_VOID); registerBiome(bclBiome, Dimension.END_VOID);
return bclBiome; return bclBiome;
@ -530,7 +530,7 @@ public class BiomeAPI {
public static void registerBiomeModification(ResourceKey dimensionID, public static void registerBiomeModification(ResourceKey dimensionID,
BiConsumer<ResourceLocation, Holder<Biome>> modification) { BiConsumer<ResourceLocation, Holder<Biome>> modification) {
List<BiConsumer<ResourceLocation, Holder<Biome>>> modifications = MODIFICATIONS.computeIfAbsent(dimensionID, List<BiConsumer<ResourceLocation, Holder<Biome>>> modifications = MODIFICATIONS.computeIfAbsent(dimensionID,
k -> Lists.newArrayList()); k -> Lists.newArrayList());
modifications.add(modification); modifications.add(modification);
} }
@ -596,7 +596,7 @@ public class BiomeAPI {
public static void onFinishingBiomeTags(ResourceKey dimensionID, public static void onFinishingBiomeTags(ResourceKey dimensionID,
BiConsumer<ResourceLocation, Holder<Biome>> modification) { BiConsumer<ResourceLocation, Holder<Biome>> modification) {
List<BiConsumer<ResourceLocation, Holder<Biome>>> modifications = TAG_ADDERS.computeIfAbsent(dimensionID, List<BiConsumer<ResourceLocation, Holder<Biome>>> modifications = TAG_ADDERS.computeIfAbsent(dimensionID,
k -> Lists.newArrayList()); k -> Lists.newArrayList());
modifications.add(modification); modifications.add(modification);
} }

View file

@ -8,7 +8,7 @@ import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
import org.betterx.bclib.gui.worldgen.WorldSetupScreen; import org.betterx.bclib.gui.worldgen.WorldSetupScreen;
import org.betterx.bclib.presets.WorldPresets; import org.betterx.bclib.presets.worldgen.WorldPresets;
import java.util.Optional; import java.util.Optional;

View file

@ -1,7 +1,11 @@
package org.betterx.bclib.interfaces; package org.betterx.bclib.interfaces;
import net.minecraft.core.Registry;
import net.minecraft.world.level.levelgen.NoiseGeneratorSettings; import net.minecraft.world.level.levelgen.NoiseGeneratorSettings;
import net.minecraft.world.level.levelgen.synth.NormalNoise;
public interface NoiseGeneratorSettingsProvider { public interface NoiseGeneratorSettingsProvider {
NoiseGeneratorSettings bclib_getNoiseGeneratorSettings(); NoiseGeneratorSettings bclib_getNoiseGeneratorSettings();
Registry<NormalNoise.NoiseParameters> bclib_getNoises();
} }

View file

@ -13,7 +13,7 @@ import net.minecraft.world.level.levelgen.presets.WorldPreset;
import com.mojang.datafixers.util.Pair; import com.mojang.datafixers.util.Pair;
import org.betterx.bclib.api.biomes.BiomeAPI; import org.betterx.bclib.api.biomes.BiomeAPI;
import org.betterx.bclib.presets.WorldPresets; import org.betterx.bclib.presets.worldgen.WorldPresets;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;

View file

@ -0,0 +1,22 @@
package org.betterx.bclib.mixin.common;
import net.minecraft.core.Registry;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.chunk.ChunkGenerators;
import com.mojang.serialization.Codec;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.presets.worldgen.BCLChunkGenerator;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(ChunkGenerators.class)
public class ChunkGeneratorsMixin {
@Inject(method = "bootstrap", at = @At(value = "HEAD"))
private static void bcl_bootstrap(Registry<Codec<? extends ChunkGenerator>> registry,
CallbackInfoReturnable<Codec<? extends ChunkGenerator>> cir) {
Registry.register(registry, BCLib.makeID("betterx"), BCLChunkGenerator.CODEC);
}
}

View file

@ -2,7 +2,7 @@ package org.betterx.bclib.mixin.common;
import net.minecraft.server.dedicated.DedicatedServerProperties; import net.minecraft.server.dedicated.DedicatedServerProperties;
import org.betterx.bclib.presets.WorldPresets; import org.betterx.bclib.presets.worldgen.WorldPresets;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg; import org.spongepowered.asm.mixin.injection.ModifyArg;
@ -11,7 +11,7 @@ import org.spongepowered.asm.mixin.injection.ModifyArg;
public class DedicatedServerPropertiesMixin { public class DedicatedServerPropertiesMixin {
//Make sure the default server properties use our Default World Preset //Make sure the default server properties use our Default World Preset
@ModifyArg(method = "<init>", index = 3, at = @At(value = "INVOKE", target = "Lnet/minecraft/server/dedicated/DedicatedServerProperties$WorldGenProperties;<init>(Ljava/lang/String;Lcom/google/gson/JsonObject;ZLjava/lang/String;)V")) @ModifyArg(method = "<init>", index = 3, at = @At(value = "INVOKE", target = "Lnet/minecraft/server/dedicated/DedicatedServerProperties$WorldGenProperties;<init>(Ljava/lang/String;Lcom/google/gson/JsonObject;ZLjava/lang/String;)V"))
private String bcl_foo(String levelType) { private String bcl_init(String levelType) {
return WorldPresets.DEFAULT.orElseThrow().location().toString(); return WorldPresets.DEFAULT.orElseThrow().location().toString();
} }
} }

View file

@ -22,14 +22,14 @@ public class MultiPackResourceManagerMixin {
@Inject(method = "getResource", at = @At("HEAD"), cancellable = true) @Inject(method = "getResource", at = @At("HEAD"), cancellable = true)
private void bclib_hasResource(ResourceLocation resourceLocation, CallbackInfoReturnable<Optional<Resource>> info) { private void bclib_hasResource(ResourceLocation resourceLocation, CallbackInfoReturnable<Optional<Resource>> info) {
if (resourceLocation.getNamespace().equals("minecraft")) { // if (resourceLocation.getNamespace().equals("minecraft")) {
for (String key : BCLIB_MISSING_RESOURCES) { // for (String key : BCLIB_MISSING_RESOURCES) {
if (resourceLocation.getPath().equals(key)) { // if (resourceLocation.getPath().equals(key)) {
info.setReturnValue(Optional.empty()); // info.setReturnValue(Optional.empty());
info.cancel(); // info.cancel();
return; // return;
} // }
} // }
} // }
} }
} }

View file

@ -1,16 +1,14 @@
package org.betterx.bclib.mixin.common; package org.betterx.bclib.mixin.common;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder; import net.minecraft.core.Holder;
import net.minecraft.server.level.ServerLevel; import net.minecraft.core.Registry;
import net.minecraft.world.level.StructureManager; import net.minecraft.world.level.StructureManager;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.levelgen.*; import net.minecraft.world.level.levelgen.*;
import net.minecraft.world.level.levelgen.blending.Blender; import net.minecraft.world.level.levelgen.blending.Blender;
import net.minecraft.world.level.levelgen.carver.CarvingContext; import net.minecraft.world.level.levelgen.synth.NormalNoise;
import org.betterx.bclib.interfaces.NoiseGeneratorSettingsProvider; import org.betterx.bclib.interfaces.NoiseGeneratorSettingsProvider;
import org.betterx.bclib.interfaces.SurfaceProvider; import org.betterx.bclib.interfaces.SurfaceProvider;
@ -19,7 +17,6 @@ import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Shadow;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.util.Optional;
@Mixin(NoiseBasedChunkGenerator.class) @Mixin(NoiseBasedChunkGenerator.class)
public abstract class NoiseBasedChunkGeneratorMixin implements SurfaceProvider, NoiseGeneratorSettingsProvider { public abstract class NoiseBasedChunkGeneratorMixin implements SurfaceProvider, NoiseGeneratorSettingsProvider {
@ -27,6 +24,9 @@ public abstract class NoiseBasedChunkGeneratorMixin implements SurfaceProvider,
@Shadow @Shadow
protected Holder<NoiseGeneratorSettings> settings; protected Holder<NoiseGeneratorSettings> settings;
@Shadow
@Final
private Registry<NormalNoise.NoiseParameters> noises;
@Final @Final
@Shadow @Shadow
private Aquifer.FluidPicker globalFluidPicker; private Aquifer.FluidPicker globalFluidPicker;
@ -39,32 +39,15 @@ public abstract class NoiseBasedChunkGeneratorMixin implements SurfaceProvider,
return settings.value(); return settings.value();
} }
@Override
public Registry<NormalNoise.NoiseParameters> bclib_getNoises() {
return noises;
}
@Shadow @Shadow
protected abstract NoiseChunk createNoiseChunk(ChunkAccess chunkAccess, protected abstract NoiseChunk createNoiseChunk(ChunkAccess chunkAccess,
StructureManager structureManager, StructureManager structureManager,
Blender blender, Blender blender,
RandomState randomState); RandomState randomState);
@Override
@SuppressWarnings("deprecation")
public BlockState bclib_getSurface(BlockPos pos, Holder<Biome> biome, ServerLevel level) {
ChunkAccess chunkAccess = level.getChunk(pos.getX() >> 4, pos.getZ() >> 4);
StructureManager structureManager = level.structureManager();
NoiseBasedChunkGenerator generator = NoiseBasedChunkGenerator.class.cast(this);
RandomState randomState = level.getChunkSource().randomState();
NoiseChunk noiseChunk = chunkAccess.getOrCreateNoiseChunk(ca -> this.createNoiseChunk(ca,
structureManager,
Blender.empty(),
randomState));
CarvingContext carvingContext = new CarvingContext(generator,
level.registryAccess(),
chunkAccess.getHeightAccessorForGeneration(),
noiseChunk,
randomState,
this.settings.value().surfaceRule());
Optional<BlockState> optional = carvingContext.topMaterial(bpos -> biome, chunkAccess, pos, false);
return optional.isPresent() ? optional.get() : bclib_air;
}
} }

View file

@ -12,6 +12,8 @@ import java.util.Map;
@Mixin(PresetEditor.class) @Mixin(PresetEditor.class)
interface PresetEditorMixin { interface PresetEditorMixin {
//Make Sure the PresetEditor.EDITORS Field is a mutable List. Allows us to add new Custom WorldPreset UIs in
//WorldPresetsUI
@Redirect(method = "<clinit>", at = @At(value = "INVOKE", target = "Ljava/util/Map;of(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/Map;")) @Redirect(method = "<clinit>", at = @At(value = "INVOKE", target = "Ljava/util/Map;of(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/Map;"))
private static <K, V> Map<K, V> bcl_foo(K k1, V v1, K k2, V v2) { private static <K, V> Map<K, V> bcl_foo(K k1, V v1, K k2, V v2) {
Map<K, V> a = Maps.newHashMap(); Map<K, V> a = Maps.newHashMap();

View file

@ -4,7 +4,7 @@ import net.minecraft.resources.ResourceKey;
import net.minecraft.server.dedicated.DedicatedServerProperties; import net.minecraft.server.dedicated.DedicatedServerProperties;
import net.minecraft.world.level.levelgen.presets.WorldPreset; import net.minecraft.world.level.levelgen.presets.WorldPreset;
import org.betterx.bclib.presets.WorldPresets; import org.betterx.bclib.presets.worldgen.WorldPresets;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg; import org.spongepowered.asm.mixin.injection.ModifyArg;

View file

@ -51,17 +51,17 @@ public abstract class WorldPresetsBootstrapMixin {
@ModifyArg(method = "run", at = @At(value = "INVOKE", ordinal = 0, target = "Lnet/minecraft/world/level/levelgen/presets/WorldPresets$Bootstrap;registerCustomOverworldPreset(Lnet/minecraft/resources/ResourceKey;Lnet/minecraft/world/level/dimension/LevelStem;)Lnet/minecraft/core/Holder;")) @ModifyArg(method = "run", at = @At(value = "INVOKE", ordinal = 0, target = "Lnet/minecraft/world/level/levelgen/presets/WorldPresets$Bootstrap;registerCustomOverworldPreset(Lnet/minecraft/resources/ResourceKey;Lnet/minecraft/world/level/dimension/LevelStem;)Lnet/minecraft/core/Holder;"))
private LevelStem bcl_getOverworldStem(LevelStem overworldStem) { private LevelStem bcl_getOverworldStem(LevelStem overworldStem) {
WorldPreset preset = new org.betterx.bclib.presets.WorldPresets.SortableWorldPreset( WorldPreset preset = new org.betterx.bclib.presets.worldgen.WorldPresets.SortableWorldPreset(
Map.of(LevelStem.OVERWORLD, Map.of(LevelStem.OVERWORLD,
overworldStem, overworldStem,
LevelStem.NETHER, LevelStem.NETHER,
org.betterx.bclib.presets.WorldPresets.getBCLNetherLevelStem(this.biomes, org.betterx.bclib.presets.worldgen.WorldPresets.getBCLNetherLevelStem(this.biomes,
this.netherDimensionType, this.netherDimensionType,
this.structureSets, this.structureSets,
this.noises, this.noises,
this.netherNoiseSettings), this.netherNoiseSettings),
LevelStem.END, LevelStem.END,
org.betterx.bclib.presets.WorldPresets.getBCLEndLevelStem(this.biomes, org.betterx.bclib.presets.worldgen.WorldPresets.getBCLEndLevelStem(this.biomes,
this.endDimensionType, this.endDimensionType,
this.structureSets, this.structureSets,
this.noises, this.noises,
@ -69,7 +69,7 @@ public abstract class WorldPresetsBootstrapMixin {
), 0 ), 0
); );
BuiltinRegistries.register(this.presets, org.betterx.bclib.presets.WorldPresets.BCL_WORLD, preset); BuiltinRegistries.register(this.presets, org.betterx.bclib.presets.worldgen.WorldPresets.BCL_WORLD, preset);
return overworldStem; return overworldStem;
} }

View file

@ -1,4 +1,4 @@
package org.betterx.bclib.presets; package org.betterx.bclib.presets.worldgen;
import net.minecraft.core.Holder; import net.minecraft.core.Holder;
import net.minecraft.core.Registry; import net.minecraft.core.Registry;
@ -11,7 +11,6 @@ import net.minecraft.util.RandomSource;
import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.dimension.LevelStem; import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
import net.minecraft.world.level.levelgen.NoiseGeneratorSettings; import net.minecraft.world.level.levelgen.NoiseGeneratorSettings;
import net.minecraft.world.level.levelgen.WorldGenSettings; import net.minecraft.world.level.levelgen.WorldGenSettings;
import net.minecraft.world.level.levelgen.presets.WorldPreset; import net.minecraft.world.level.levelgen.presets.WorldPreset;
@ -39,7 +38,7 @@ public class WorldPresets {
BCLibNetherBiomeSource netherSource = new BCLibNetherBiomeSource(biomes); BCLibNetherBiomeSource netherSource = new BCLibNetherBiomeSource(biomes);
LevelStem bclNether = new LevelStem( LevelStem bclNether = new LevelStem(
dimension, dimension,
new NoiseBasedChunkGenerator( new BCLChunkGenerator(
structureSets, structureSets,
noiseParameters, noiseParameters,
netherSource, netherSource,
@ -57,7 +56,7 @@ public class WorldPresets {
BCLibEndBiomeSource netherSource = new BCLibEndBiomeSource(biomes); BCLibEndBiomeSource netherSource = new BCLibEndBiomeSource(biomes);
LevelStem bclEnd = new LevelStem( LevelStem bclEnd = new LevelStem(
dimension, dimension,
new NoiseBasedChunkGenerator( new BCLChunkGenerator(
structureSets, structureSets,
noiseParameters, noiseParameters,
netherSource, netherSource,

View file

@ -33,18 +33,18 @@ public class BCLibNetherBiomeSource extends BCLBiomeSource {
public static final Codec<BCLibNetherBiomeSource> CODEC = RecordCodecBuilder public static final Codec<BCLibNetherBiomeSource> CODEC = RecordCodecBuilder
.create(instance -> instance .create(instance -> instance
.group(RegistryOps .group(RegistryOps
.retrieveRegistry(Registry.BIOME_REGISTRY) .retrieveRegistry(Registry.BIOME_REGISTRY)
.forGetter(source -> source.biomeRegistry), .forGetter(source -> source.biomeRegistry),
Codec Codec
.LONG .LONG
.fieldOf("seed") .fieldOf("seed")
.stable() .stable()
.forGetter(source -> { .forGetter(source -> {
return source.currentSeed; return source.currentSeed;
}) })
) )
.apply(instance, instance.stable(BCLibNetherBiomeSource::new)) .apply(instance, instance.stable(BCLibNetherBiomeSource::new))
); );
private BiomeMap biomeMap; private BiomeMap biomeMap;
private final BiomePicker biomePicker; private final BiomePicker biomePicker;
@ -172,8 +172,8 @@ public class BCLibNetherBiomeSource extends BCLBiomeSource {
); );
} else { } else {
this.biomeMap = mapConstructor.apply(seed, this.biomeMap = mapConstructor.apply(seed,
GeneratorOptions.getBiomeSizeNether(), GeneratorOptions.getBiomeSizeNether(),
biomePicker); biomePicker);
} }
} }

View file

@ -9,4 +9,4 @@ accessible class net/minecraft/world/level/levelgen/SurfaceRules$LazyXZCondition
accessible class net/minecraft/world/level/levelgen/SurfaceRules$LazyCondition accessible class net/minecraft/world/level/levelgen/SurfaceRules$LazyCondition
accessible class net/minecraft/world/level/levelgen/SurfaceRules$SequenceRuleSource accessible class net/minecraft/world/level/levelgen/SurfaceRules$SequenceRuleSource
accessible class net/minecraft/world/level/levelgen/presets/WorldPresets$Bootstrap accessible class net/minecraft/world/level/levelgen/presets/WorldPresets$Bootstrap
extendable class net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator

View file

@ -12,6 +12,7 @@
"BlockStateBaseMixin", "BlockStateBaseMixin",
"BoneMealItemMixin", "BoneMealItemMixin",
"ChunkGeneratorMixin", "ChunkGeneratorMixin",
"ChunkGeneratorsMixin",
"ComposterBlockAccessor", "ComposterBlockAccessor",
"CraftingMenuMixin", "CraftingMenuMixin",
"DedicatedServerPropertiesMixin", "DedicatedServerPropertiesMixin",

View file

@ -1,7 +1,7 @@
{ {
"schemaVersion": 1, "schemaVersion": 1,
"id": "bclib", "id": "bclib",
"version": "${version}", "version": "2.0.0",
"name": "BCLib", "name": "BCLib",
"description": "A library for BetterX team mods", "description": "A library for BetterX team mods",
"authors": [ "authors": [