Reorganized Imports/Packages
This commit is contained in:
parent
cb9459f176
commit
3ee10482ab
721 changed files with 34873 additions and 33558 deletions
65
src/main/java/org/betterx/bclib/BCLib.java
Normal file
65
src/main/java/org/betterx/bclib/BCLib.java
Normal file
|
@ -0,0 +1,65 @@
|
|||
package org.betterx.bclib;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
|
||||
import org.betterx.bclib.api.WorldDataAPI;
|
||||
import org.betterx.bclib.api.dataexchange.DataExchangeAPI;
|
||||
import org.betterx.bclib.api.dataexchange.handler.autosync.*;
|
||||
import org.betterx.bclib.api.tag.TagAPI;
|
||||
import org.betterx.bclib.config.Configs;
|
||||
import org.betterx.bclib.recipes.AnvilRecipe;
|
||||
import org.betterx.bclib.recipes.CraftingRecipes;
|
||||
import org.betterx.bclib.registry.BaseBlockEntities;
|
||||
import org.betterx.bclib.registry.BaseRegistry;
|
||||
import org.betterx.bclib.util.Logger;
|
||||
import org.betterx.bclib.world.generator.BCLibEndBiomeSource;
|
||||
import org.betterx.bclib.world.generator.BCLibNetherBiomeSource;
|
||||
import org.betterx.bclib.world.generator.GeneratorOptions;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class BCLib implements ModInitializer {
|
||||
public static final String MOD_ID = "bclib";
|
||||
public static final Logger LOGGER = new Logger(MOD_ID);
|
||||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
BaseRegistry.register();
|
||||
GeneratorOptions.init();
|
||||
BaseBlockEntities.register();
|
||||
BCLibEndBiomeSource.register();
|
||||
BCLibNetherBiomeSource.register();
|
||||
TagAPI.init();
|
||||
CraftingRecipes.init();
|
||||
WorldDataAPI.registerModCache(MOD_ID);
|
||||
DataExchangeAPI.registerMod(MOD_ID);
|
||||
AnvilRecipe.register();
|
||||
|
||||
DataExchangeAPI.registerDescriptors(List.of(
|
||||
HelloClient.DESCRIPTOR,
|
||||
HelloServer.DESCRIPTOR,
|
||||
RequestFiles.DESCRIPTOR,
|
||||
SendFiles.DESCRIPTOR,
|
||||
Chunker.DESCRIPTOR
|
||||
));
|
||||
|
||||
BCLibPatch.register();
|
||||
Configs.save();
|
||||
}
|
||||
|
||||
public static boolean isDevEnvironment() {
|
||||
return FabricLoader.getInstance().isDevelopmentEnvironment();
|
||||
}
|
||||
|
||||
public static boolean isClient() {
|
||||
return FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT;
|
||||
}
|
||||
|
||||
public static ResourceLocation makeID(String path) {
|
||||
return new ResourceLocation(MOD_ID, path);
|
||||
}
|
||||
}
|
95
src/main/java/org/betterx/bclib/BCLibPatch.java
Normal file
95
src/main/java/org/betterx/bclib/BCLibPatch.java
Normal file
|
@ -0,0 +1,95 @@
|
|||
package org.betterx.bclib;
|
||||
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
|
||||
import org.betterx.bclib.api.datafixer.DataFixerAPI;
|
||||
import org.betterx.bclib.api.datafixer.ForcedLevelPatch;
|
||||
import org.betterx.bclib.api.datafixer.MigrationProfile;
|
||||
import org.betterx.bclib.config.Configs;
|
||||
import org.betterx.bclib.world.generator.GeneratorOptions;
|
||||
|
||||
public final class BCLibPatch {
|
||||
public static void register() {
|
||||
// TODO separate values in config on client side (config screen)
|
||||
if (Configs.MAIN_CONFIG.repairBiomes() && (GeneratorOptions.fixEndBiomeSource() || GeneratorOptions.fixNetherBiomeSource())) {
|
||||
DataFixerAPI.registerPatch(BiomeSourcePatch::new);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class BiomeSourcePatch extends ForcedLevelPatch {
|
||||
private static final String NETHER_BIOME_SOURCE = "bclib:nether_biome_source";
|
||||
private static final String END_BIOME_SOURCE = "bclib:end_biome_source";
|
||||
private static final String MC_NETHER = "minecraft:the_nether";
|
||||
private static final String MC_END = "minecraft:the_end";
|
||||
|
||||
protected BiomeSourcePatch() {
|
||||
super(BCLib.MOD_ID, "1.2.1");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean runLevelDatPatch(CompoundTag root, MigrationProfile profile) {
|
||||
CompoundTag worldGenSettings = root.getCompound("Data").getCompound("WorldGenSettings");
|
||||
CompoundTag dimensions = worldGenSettings.getCompound("dimensions");
|
||||
long seed = worldGenSettings.getLong("seed");
|
||||
boolean result = false;
|
||||
|
||||
if (GeneratorOptions.fixNetherBiomeSource()) {
|
||||
if (!dimensions.contains(MC_NETHER) || !isBCLibEntry(dimensions.getCompound(MC_NETHER))) {
|
||||
CompoundTag dimRoot = new CompoundTag();
|
||||
dimRoot.put("generator", makeNetherGenerator(seed));
|
||||
dimRoot.putString("type", MC_NETHER);
|
||||
dimensions.put(MC_NETHER, dimRoot);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (GeneratorOptions.fixEndBiomeSource()) {
|
||||
if (!dimensions.contains(MC_END) || !isBCLibEntry(dimensions.getCompound(MC_END))) {
|
||||
CompoundTag dimRoot = new CompoundTag();
|
||||
dimRoot.put("generator", makeEndGenerator(seed));
|
||||
dimRoot.putString("type", MC_END);
|
||||
dimensions.put(MC_END, dimRoot);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean isBCLibEntry(CompoundTag dimRoot) {
|
||||
String type = dimRoot.getCompound("generator").getCompound("biome_source").getString("type");
|
||||
if (type.isEmpty() || type.length() < 5) {
|
||||
return false;
|
||||
}
|
||||
return type.startsWith("bclib");
|
||||
}
|
||||
|
||||
public static CompoundTag makeNetherGenerator(long seed) {
|
||||
CompoundTag generator = new CompoundTag();
|
||||
generator.putString("type", "minecraft:noise");
|
||||
generator.putString("settings", "minecraft:nether");
|
||||
generator.putLong("seed", seed);
|
||||
|
||||
CompoundTag biomeSource = new CompoundTag();
|
||||
biomeSource.putString("type", NETHER_BIOME_SOURCE);
|
||||
biomeSource.putLong("seed", seed);
|
||||
generator.put("biome_source", biomeSource);
|
||||
|
||||
return generator;
|
||||
}
|
||||
|
||||
public static CompoundTag makeEndGenerator(long seed) {
|
||||
CompoundTag generator = new CompoundTag();
|
||||
generator.putString("type", "minecraft:noise");
|
||||
generator.putString("settings", "minecraft:end");
|
||||
generator.putLong("seed", seed);
|
||||
|
||||
CompoundTag biomeSource = new CompoundTag();
|
||||
biomeSource.putString("type", END_BIOME_SOURCE);
|
||||
biomeSource.putLong("seed", seed);
|
||||
generator.put("biome_source", biomeSource);
|
||||
|
||||
return generator;
|
||||
}
|
||||
}
|
142
src/main/java/org/betterx/bclib/api/BonemealAPI.java
Normal file
142
src/main/java/org/betterx/bclib/api/BonemealAPI.java
Normal file
|
@ -0,0 +1,142 @@
|
|||
package org.betterx.bclib.api;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import org.betterx.bclib.util.WeightedList;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class BonemealAPI {
|
||||
private static final Map<ResourceLocation, Map<Block, WeightedList<Block>>> WATER_GRASS_BIOMES = Maps.newHashMap();
|
||||
private static final Map<ResourceLocation, Map<Block, WeightedList<Block>>> LAND_GRASS_BIOMES = Maps.newHashMap();
|
||||
private static final Map<Block, WeightedList<Block>> WATER_GRASS_TYPES = Maps.newHashMap();
|
||||
private static final Map<Block, WeightedList<Block>> LAND_GRASS_TYPES = Maps.newHashMap();
|
||||
private static final Map<Block, Block> SPREADABLE_BLOCKS = Maps.newHashMap();
|
||||
private static final Set<Block> TERRAIN_TO_SPREAD = Sets.newHashSet();
|
||||
private static final Set<Block> TERRAIN = Sets.newHashSet();
|
||||
|
||||
public static void addSpreadableBlock(Block spreadableBlock, Block surfaceForSpread) {
|
||||
SPREADABLE_BLOCKS.put(spreadableBlock, surfaceForSpread);
|
||||
TERRAIN_TO_SPREAD.add(surfaceForSpread);
|
||||
TERRAIN.add(surfaceForSpread);
|
||||
}
|
||||
|
||||
public static boolean isTerrain(Block block) {
|
||||
return TERRAIN.contains(block);
|
||||
}
|
||||
|
||||
public static boolean isSpreadableTerrain(Block block) {
|
||||
return TERRAIN_TO_SPREAD.contains(block);
|
||||
}
|
||||
|
||||
public static Block getSpreadable(Block block) {
|
||||
return SPREADABLE_BLOCKS.get(block);
|
||||
}
|
||||
|
||||
public static void addLandGrass(Block plant, Block... terrain) {
|
||||
for (Block block : terrain) {
|
||||
addLandGrass(plant, block, 1F);
|
||||
}
|
||||
}
|
||||
|
||||
public static void addLandGrass(ResourceLocation biome, Block plant, Block... terrain) {
|
||||
for (Block block : terrain) {
|
||||
addLandGrass(biome, plant, block, 1F);
|
||||
}
|
||||
}
|
||||
|
||||
public static void addLandGrass(Block plant, Block terrain, float chance) {
|
||||
WeightedList<Block> list = LAND_GRASS_TYPES.get(terrain);
|
||||
if (list == null) {
|
||||
list = new WeightedList<Block>();
|
||||
LAND_GRASS_TYPES.put(terrain, list);
|
||||
}
|
||||
TERRAIN.add(terrain);
|
||||
list.add(plant, chance);
|
||||
}
|
||||
|
||||
public static void addLandGrass(ResourceLocation biome, Block plant, Block terrain, float chance) {
|
||||
Map<Block, WeightedList<Block>> map = LAND_GRASS_BIOMES.get(biome);
|
||||
if (map == null) {
|
||||
map = Maps.newHashMap();
|
||||
LAND_GRASS_BIOMES.put(biome, map);
|
||||
}
|
||||
WeightedList<Block> list = map.get(terrain);
|
||||
if (list == null) {
|
||||
list = new WeightedList<Block>();
|
||||
map.put(terrain, list);
|
||||
}
|
||||
TERRAIN.add(terrain);
|
||||
list.add(plant, chance);
|
||||
}
|
||||
|
||||
public static void addWaterGrass(Block plant, Block... terrain) {
|
||||
for (Block block : terrain) {
|
||||
addWaterGrass(plant, block, 1F);
|
||||
}
|
||||
}
|
||||
|
||||
public static void addWaterGrass(ResourceLocation biome, Block plant, Block... terrain) {
|
||||
for (Block block : terrain) {
|
||||
addWaterGrass(biome, plant, block, 1F);
|
||||
}
|
||||
}
|
||||
|
||||
public static void addWaterGrass(Block plant, Block terrain, float chance) {
|
||||
WeightedList<Block> list = WATER_GRASS_TYPES.get(terrain);
|
||||
if (list == null) {
|
||||
list = new WeightedList<Block>();
|
||||
WATER_GRASS_TYPES.put(terrain, list);
|
||||
}
|
||||
TERRAIN.add(terrain);
|
||||
list.add(plant, chance);
|
||||
}
|
||||
|
||||
public static void addWaterGrass(ResourceLocation biome, Block plant, Block terrain, float chance) {
|
||||
Map<Block, WeightedList<Block>> map = WATER_GRASS_BIOMES.get(biome);
|
||||
if (map == null) {
|
||||
map = Maps.newHashMap();
|
||||
WATER_GRASS_BIOMES.put(biome, map);
|
||||
}
|
||||
WeightedList<Block> list = map.get(terrain);
|
||||
if (list == null) {
|
||||
list = new WeightedList<Block>();
|
||||
map.put(terrain, list);
|
||||
}
|
||||
TERRAIN.add(terrain);
|
||||
list.add(plant, chance);
|
||||
}
|
||||
|
||||
public static Block getLandGrass(ResourceLocation biomeID, Block terrain, RandomSource random) {
|
||||
Map<Block, WeightedList<Block>> map = LAND_GRASS_BIOMES.get(biomeID);
|
||||
WeightedList<Block> list = null;
|
||||
if (map != null) {
|
||||
list = map.get(terrain);
|
||||
if (list == null) {
|
||||
list = LAND_GRASS_TYPES.get(terrain);
|
||||
}
|
||||
} else {
|
||||
list = LAND_GRASS_TYPES.get(terrain);
|
||||
}
|
||||
return list == null ? null : list.get(random);
|
||||
}
|
||||
|
||||
public static Block getWaterGrass(ResourceLocation biomeID, Block terrain, RandomSource random) {
|
||||
Map<Block, WeightedList<Block>> map = WATER_GRASS_BIOMES.get(biomeID);
|
||||
WeightedList<Block> list = null;
|
||||
if (map != null) {
|
||||
list = map.get(terrain);
|
||||
if (list == null) {
|
||||
list = WATER_GRASS_TYPES.get(terrain);
|
||||
}
|
||||
} else {
|
||||
list = WATER_GRASS_TYPES.get(terrain);
|
||||
}
|
||||
return list == null ? null : list.get(random);
|
||||
}
|
||||
}
|
23
src/main/java/org/betterx/bclib/api/ComposterAPI.java
Normal file
23
src/main/java/org/betterx/bclib/api/ComposterAPI.java
Normal file
|
@ -0,0 +1,23 @@
|
|||
package org.betterx.bclib.api;
|
||||
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
|
||||
import org.betterx.bclib.mixin.common.ComposterBlockAccessor;
|
||||
|
||||
public class ComposterAPI {
|
||||
public static Block allowCompost(float chance, Block block) {
|
||||
if (block != null) {
|
||||
allowCompost(chance, block.asItem());
|
||||
}
|
||||
return block;
|
||||
}
|
||||
|
||||
public static Item allowCompost(float chance, Item item) {
|
||||
if (item != null && item != Items.AIR) {
|
||||
ComposterBlockAccessor.callAdd(chance, item);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
}
|
141
src/main/java/org/betterx/bclib/api/LifeCycleAPI.java
Normal file
141
src/main/java/org/betterx/bclib/api/LifeCycleAPI.java
Normal file
|
@ -0,0 +1,141 @@
|
|||
package org.betterx.bclib.api;
|
||||
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.progress.ChunkProgressListener;
|
||||
import net.minecraft.world.level.CustomSpawner;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||
import net.minecraft.world.level.storage.ServerLevelData;
|
||||
|
||||
import org.betterx.bclib.api.datafixer.DataFixerAPI;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* provides some lifetime hooks for a Minecraft instance
|
||||
*/
|
||||
public class LifeCycleAPI {
|
||||
private final static List<LevelLoadBiomesCall> onLoadLevelBiomes = new ArrayList<>(2);
|
||||
private final static List<LevelLoadCall> onLoadLevel = new ArrayList<>(2);
|
||||
private final static List<BeforeLevelLoadCall> beforeLoadLevel = new ArrayList<>(2);
|
||||
|
||||
/**
|
||||
* A callback function that is used for each new ServerLevel instance
|
||||
*/
|
||||
public interface BeforeLevelLoadCall {
|
||||
void beforeLoad();
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback function that is used for each new ServerLevel instance
|
||||
*/
|
||||
public interface LevelLoadBiomesCall {
|
||||
void onLoad(ServerLevel world, long seed, Registry<Biome> registry);
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback function that is used for each new ServerLevel instance
|
||||
*/
|
||||
public interface LevelLoadCall {
|
||||
void onLoad(
|
||||
ServerLevel world,
|
||||
MinecraftServer minecraftServer,
|
||||
Executor executor,
|
||||
LevelStorageSource.LevelStorageAccess levelStorageAccess,
|
||||
ServerLevelData serverLevelData,
|
||||
ResourceKey<Level> resourceKey,
|
||||
ChunkProgressListener chunkProgressListener,
|
||||
boolean bl,
|
||||
long l,
|
||||
List<CustomSpawner> list,
|
||||
boolean bl2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback that is called before a level is loaded or created,
|
||||
* but after the {@link WorldDataAPI} was initialized and patches from
|
||||
* the {@link DataFixerAPI} were applied.
|
||||
*
|
||||
* @param call The callback Method
|
||||
*/
|
||||
public static void beforeLevelLoad(BeforeLevelLoadCall call) {
|
||||
beforeLoadLevel.add(call);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback that is called when a new {@code ServerLevel is instantiated}.
|
||||
* This callback will receive the world seed as well as it's biome registry.
|
||||
*
|
||||
* @param call The calbback Method
|
||||
*/
|
||||
public static void onLevelLoad(LevelLoadBiomesCall call) {
|
||||
onLoadLevelBiomes.add(call);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback that is called when a new {@code ServerLevel is instantiated}.
|
||||
* This callbacl will receiv all parameters that were passed to the ServerLevel's constructor
|
||||
*
|
||||
* @param call The calbback Method
|
||||
*/
|
||||
public static void onLevelLoad(LevelLoadCall call) {
|
||||
onLoadLevel.add(call);
|
||||
}
|
||||
|
||||
/**
|
||||
* For internal use, You should not call this method!
|
||||
*/
|
||||
public static void _runBeforeLevelLoad() {
|
||||
beforeLoadLevel.forEach(c -> c.beforeLoad());
|
||||
}
|
||||
|
||||
/**
|
||||
* For internal use, You should not call this method!
|
||||
*
|
||||
* @param minecraftServer
|
||||
* @param executor
|
||||
* @param levelStorageAccess
|
||||
* @param serverLevelData
|
||||
* @param resourceKey
|
||||
* @param chunkProgressListener
|
||||
* @param bl
|
||||
* @param l
|
||||
* @param list
|
||||
* @param bl2
|
||||
*/
|
||||
public static void _runLevelLoad(ServerLevel world,
|
||||
MinecraftServer minecraftServer,
|
||||
Executor executor,
|
||||
LevelStorageSource.LevelStorageAccess levelStorageAccess,
|
||||
ServerLevelData serverLevelData,
|
||||
ResourceKey<Level> resourceKey,
|
||||
ChunkProgressListener chunkProgressListener,
|
||||
boolean bl,
|
||||
long l,
|
||||
List<CustomSpawner> list,
|
||||
boolean bl2) {
|
||||
onLoadLevel.forEach(c -> c.onLoad(
|
||||
world,
|
||||
minecraftServer,
|
||||
executor,
|
||||
levelStorageAccess,
|
||||
serverLevelData,
|
||||
resourceKey,
|
||||
chunkProgressListener,
|
||||
bl,
|
||||
l,
|
||||
list,
|
||||
bl2)
|
||||
);
|
||||
|
||||
final long seed = world.getSeed();
|
||||
final Registry<Biome> biomeRegistry = world.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY);
|
||||
onLoadLevelBiomes.forEach(c -> c.onLoad(world, seed, biomeRegistry));
|
||||
}
|
||||
}
|
48
src/main/java/org/betterx/bclib/api/ModIntegrationAPI.java
Normal file
48
src/main/java/org/betterx/bclib/api/ModIntegrationAPI.java
Normal file
|
@ -0,0 +1,48 @@
|
|||
package org.betterx.bclib.api;
|
||||
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import org.betterx.bclib.integration.ModIntegration;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ModIntegrationAPI {
|
||||
private static final List<ModIntegration> INTEGRATIONS = Lists.newArrayList();
|
||||
private static final boolean HAS_CANVAS = FabricLoader.getInstance().isModLoaded("canvas");
|
||||
|
||||
/**
|
||||
* Registers mod integration
|
||||
*
|
||||
* @param integration
|
||||
* @return
|
||||
*/
|
||||
public static ModIntegration register(ModIntegration integration) {
|
||||
INTEGRATIONS.add(integration);
|
||||
return integration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered mod integrations.
|
||||
*
|
||||
* @return {@link List} of {@link ModIntegration}.
|
||||
*/
|
||||
public static List<ModIntegration> getIntegrations() {
|
||||
return INTEGRATIONS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all integrations, only for internal usage.
|
||||
*/
|
||||
public static void registerAll() {
|
||||
INTEGRATIONS.forEach(integration -> {
|
||||
if (integration.modIsInstalled()) {
|
||||
integration.init();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static boolean hasCanvas() {
|
||||
return HAS_CANVAS;
|
||||
}
|
||||
}
|
148
src/main/java/org/betterx/bclib/api/PostInitAPI.java
Normal file
148
src/main/java/org/betterx/bclib/api/PostInitAPI.java
Normal file
|
@ -0,0 +1,148 @@
|
|||
package org.betterx.bclib.api;
|
||||
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.api.biomes.BiomeAPI;
|
||||
import org.betterx.bclib.api.tag.NamedMineableTags;
|
||||
import org.betterx.bclib.api.tag.TagAPI;
|
||||
import org.betterx.bclib.blocks.BaseBarrelBlock;
|
||||
import org.betterx.bclib.blocks.BaseChestBlock;
|
||||
import org.betterx.bclib.blocks.BaseFurnaceBlock;
|
||||
import org.betterx.bclib.blocks.BaseSignBlock;
|
||||
import org.betterx.bclib.client.render.BCLRenderLayer;
|
||||
import org.betterx.bclib.client.render.BaseChestBlockEntityRenderer;
|
||||
import org.betterx.bclib.client.render.BaseSignBlockEntityRenderer;
|
||||
import org.betterx.bclib.config.Configs;
|
||||
import org.betterx.bclib.interfaces.PostInitable;
|
||||
import org.betterx.bclib.interfaces.RenderLayerProvider;
|
||||
import org.betterx.bclib.interfaces.TagProvider;
|
||||
import org.betterx.bclib.interfaces.tools.*;
|
||||
import org.betterx.bclib.registry.BaseBlockEntities;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class PostInitAPI {
|
||||
private static List<Consumer<Boolean>> postInitFunctions = Lists.newArrayList();
|
||||
private static List<TagKey<Block>> blockTags = Lists.newArrayList();
|
||||
private static List<TagKey<Item>> itemTags = Lists.newArrayList();
|
||||
|
||||
/**
|
||||
* Register a new function which will be called after all mods are initiated. Will be called on both client and server.
|
||||
*
|
||||
* @param function {@link Consumer} with {@code boolean} parameter ({@code true} for client, {@code false} for server).
|
||||
*/
|
||||
public static void register(Consumer<Boolean> function) {
|
||||
postInitFunctions.add(function);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called in proper BCLib entry points, for internal usage only.
|
||||
*
|
||||
* @param isClient {@code boolean}, {@code true} for client, {@code false} for server.
|
||||
*/
|
||||
public static void postInit(boolean isClient) {
|
||||
if (postInitFunctions == null) {
|
||||
return;
|
||||
}
|
||||
postInitFunctions.forEach(function -> function.accept(isClient));
|
||||
Registry.BLOCK.forEach(block -> {
|
||||
processBlockCommon(block);
|
||||
if (isClient) {
|
||||
processBlockClient(block);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Registry.ITEM.forEach(item -> {
|
||||
processItemCommon(item);
|
||||
});
|
||||
postInitFunctions = null;
|
||||
blockTags = null;
|
||||
itemTags = null;
|
||||
BiomeAPI.loadFabricAPIBiomes();
|
||||
Configs.BIOMES_CONFIG.saveChanges();
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
private static void processBlockClient(Block block) {
|
||||
if (block instanceof RenderLayerProvider) {
|
||||
BCLRenderLayer layer = ((RenderLayerProvider) block).getRenderLayer();
|
||||
if (layer == BCLRenderLayer.CUTOUT) BlockRenderLayerMap.INSTANCE.putBlock(block, RenderType.cutout());
|
||||
else if (layer == BCLRenderLayer.TRANSLUCENT)
|
||||
BlockRenderLayerMap.INSTANCE.putBlock(block, RenderType.translucent());
|
||||
}
|
||||
if (block instanceof BaseChestBlock) {
|
||||
BaseChestBlockEntityRenderer.registerRenderLayer(block);
|
||||
} else if (block instanceof BaseSignBlock) {
|
||||
BaseSignBlockEntityRenderer.registerRenderLayer(block);
|
||||
}
|
||||
}
|
||||
|
||||
private static void processItemCommon(Item item) {
|
||||
if (item instanceof TagProvider provider) {
|
||||
try {
|
||||
provider.addTags(null, itemTags);
|
||||
} catch (NullPointerException ex) {
|
||||
BCLib.LOGGER.error(item + " probably tried to access blockTags.", ex);
|
||||
}
|
||||
itemTags.forEach(tag -> TagAPI.addItemTag(tag, item));
|
||||
itemTags.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private static void processBlockCommon(Block block) {
|
||||
if (block instanceof PostInitable) {
|
||||
((PostInitable) block).postInit();
|
||||
}
|
||||
if (block instanceof BaseChestBlock) {
|
||||
BaseBlockEntities.CHEST.registerBlock(block);
|
||||
} else if (block instanceof BaseSignBlock) {
|
||||
BaseBlockEntities.SIGN.registerBlock(block);
|
||||
} else if (block instanceof BaseBarrelBlock) {
|
||||
BaseBlockEntities.BARREL.registerBlock(block);
|
||||
} else if (block instanceof BaseFurnaceBlock) {
|
||||
BaseBlockEntities.FURNACE.registerBlock(block);
|
||||
}
|
||||
if (!(block instanceof PreventMineableAdd)) {
|
||||
if (block instanceof AddMineableShears) {
|
||||
TagAPI.addBlockTags(block, NamedMineableTags.SHEARS);
|
||||
}
|
||||
if (block instanceof AddMineableAxe) {
|
||||
TagAPI.addBlockTags(block, NamedMineableTags.AXE);
|
||||
}
|
||||
if (block instanceof AddMineablePickaxe) {
|
||||
TagAPI.addBlockTags(block, NamedMineableTags.PICKAXE);
|
||||
}
|
||||
if (block instanceof AddMineableShovel) {
|
||||
TagAPI.addBlockTags(block, NamedMineableTags.SHOVEL);
|
||||
}
|
||||
if (block instanceof AddMineableHoe) {
|
||||
TagAPI.addBlockTags(block, NamedMineableTags.HOE);
|
||||
}
|
||||
if (block instanceof AddMineableSword) {
|
||||
TagAPI.addBlockTags(block, NamedMineableTags.SWORD);
|
||||
}
|
||||
if (block instanceof AddMineableHammer) {
|
||||
TagAPI.addBlockTags(block, NamedMineableTags.HAMMER);
|
||||
}
|
||||
}
|
||||
if (block instanceof TagProvider) {
|
||||
((TagProvider) block).addTags(blockTags, itemTags);
|
||||
blockTags.forEach(tag -> TagAPI.addBlockTag(tag, block));
|
||||
itemTags.forEach(tag -> TagAPI.addItemTag(tag, block));
|
||||
blockTags.clear();
|
||||
itemTags.clear();
|
||||
}
|
||||
}
|
||||
}
|
22
src/main/java/org/betterx/bclib/api/ShovelAPI.java
Normal file
22
src/main/java/org/betterx/bclib/api/ShovelAPI.java
Normal file
|
@ -0,0 +1,22 @@
|
|||
package org.betterx.bclib.api;
|
||||
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
import org.betterx.bclib.mixin.common.ShovelItemAccessor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class ShovelAPI {
|
||||
/**
|
||||
* Will add left-click behaviour to shovel: when it is targeting cetrain {@link Block} it will be converting to new
|
||||
* {@link BlockState} on usage. Example: grass converting to path.
|
||||
*
|
||||
* @param target {@link Block} that will be converted.
|
||||
* @param convert {@link BlockState} to convert block into.
|
||||
*/
|
||||
public static void addShovelBehaviour(Block target, BlockState convert) {
|
||||
Map<Block, BlockState> map = ShovelItemAccessor.bclib_getFlattenables();
|
||||
map.put(target, convert);
|
||||
}
|
||||
}
|
146
src/main/java/org/betterx/bclib/api/WorldDataAPI.java
Normal file
146
src/main/java/org/betterx/bclib/api/WorldDataAPI.java
Normal file
|
@ -0,0 +1,146 @@
|
|||
package org.betterx.bclib.api;
|
||||
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtIo;
|
||||
import net.minecraft.world.level.storage.LevelStorageSource.LevelStorageAccess;
|
||||
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.fabricmc.loader.api.ModContainer;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.api.datafixer.DataFixerAPI;
|
||||
import org.betterx.bclib.util.ModUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Mod-specifix data-storage for a world.
|
||||
* <p>
|
||||
* This class provides the ability for mod to store persistent data inside a world. The Storage for the world is
|
||||
* currently initialized as part of the {@link DataFixerAPI} in {@link DataFixerAPI#fixData(LevelStorageAccess, boolean, Consumer)}
|
||||
* or {@link DataFixerAPI#initializeWorldData(File, boolean)}
|
||||
*/
|
||||
public class WorldDataAPI {
|
||||
private static final Map<String, CompoundTag> TAGS = Maps.newHashMap();
|
||||
private static final List<String> MODS = Lists.newArrayList();
|
||||
private static File dataDir;
|
||||
|
||||
public static void load(File dataDir) {
|
||||
WorldDataAPI.dataDir = dataDir;
|
||||
MODS.stream()
|
||||
.parallel()
|
||||
.forEach(modID -> {
|
||||
File file = new File(dataDir, modID + ".nbt");
|
||||
CompoundTag root = new CompoundTag();
|
||||
if (file.exists()) {
|
||||
try {
|
||||
root = NbtIo.readCompressed(file);
|
||||
} catch (IOException e) {
|
||||
BCLib.LOGGER.error("World data loading failed", e);
|
||||
}
|
||||
TAGS.put(modID, root);
|
||||
} else {
|
||||
Optional<ModContainer> optional = FabricLoader.getInstance()
|
||||
.getModContainer(modID);
|
||||
if (optional.isPresent()) {
|
||||
ModContainer modContainer = optional.get();
|
||||
if (BCLib.isDevEnvironment()) {
|
||||
root.putString("version", "255.255.9999");
|
||||
} else {
|
||||
root.putString("version", modContainer.getMetadata()
|
||||
.getVersion()
|
||||
.toString());
|
||||
}
|
||||
TAGS.put(modID, root);
|
||||
saveFile(modID);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register mod cache, world cache is located in world data folder.
|
||||
*
|
||||
* @param modID - {@link String} modID.
|
||||
*/
|
||||
public static void registerModCache(String modID) {
|
||||
MODS.add(modID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get root {@link CompoundTag} for mod cache in world data folder.
|
||||
*
|
||||
* @param modID - {@link String} modID.
|
||||
* @return {@link CompoundTag}
|
||||
*/
|
||||
public static CompoundTag getRootTag(String modID) {
|
||||
CompoundTag root = TAGS.get(modID);
|
||||
if (root == null) {
|
||||
root = new CompoundTag();
|
||||
TAGS.put(modID, root);
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get {@link CompoundTag} with specified path from mod cache in world data folder.
|
||||
*
|
||||
* @param modID - {@link String} path to tag, dot-separated.
|
||||
* @return {@link CompoundTag}
|
||||
*/
|
||||
public static CompoundTag getCompoundTag(String modID, String path) {
|
||||
String[] parts = path.split("\\.");
|
||||
CompoundTag tag = getRootTag(modID);
|
||||
for (String part : parts) {
|
||||
if (tag.contains(part)) {
|
||||
tag = tag.getCompound(part);
|
||||
} else {
|
||||
CompoundTag t = new CompoundTag();
|
||||
tag.put(part, t);
|
||||
tag = t;
|
||||
}
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces mod cache file to be saved.
|
||||
*
|
||||
* @param modID {@link String} mod ID.
|
||||
*/
|
||||
public static void saveFile(String modID) {
|
||||
try {
|
||||
if (!dataDir.exists()) {
|
||||
dataDir.mkdirs();
|
||||
}
|
||||
NbtIo.writeCompressed(getRootTag(modID), new File(dataDir, modID + ".nbt"));
|
||||
} catch (IOException e) {
|
||||
BCLib.LOGGER.error("World data saving failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stored mod version (only for mods with registered cache).
|
||||
*
|
||||
* @return {@link String} mod version.
|
||||
*/
|
||||
public static String getModVersion(String modID) {
|
||||
return getRootTag(modID).getString("version");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stored mod version as integer (only for mods with registered cache).
|
||||
*
|
||||
* @return {@code int} mod version.
|
||||
*/
|
||||
public static int getIntModVersion(String modID) {
|
||||
return ModUtil.convertModVersion(getModVersion(modID));
|
||||
}
|
||||
}
|
806
src/main/java/org/betterx/bclib/api/biomes/BCLBiomeBuilder.java
Normal file
806
src/main/java/org/betterx/bclib/api/biomes/BCLBiomeBuilder.java
Normal file
|
@ -0,0 +1,806 @@
|
|||
package org.betterx.bclib.api.biomes;
|
||||
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.HolderSet;
|
||||
import net.minecraft.core.particles.ParticleOptions;
|
||||
import net.minecraft.data.worldgen.BiomeDefaultFeatures;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.sounds.Music;
|
||||
import net.minecraft.sounds.SoundEvent;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.Mob;
|
||||
import net.minecraft.world.level.biome.*;
|
||||
import net.minecraft.world.level.biome.Biome.BiomeBuilder;
|
||||
import net.minecraft.world.level.biome.Biome.Precipitation;
|
||||
import net.minecraft.world.level.biome.MobSpawnSettings.SpawnerData;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.levelgen.GenerationStep;
|
||||
import net.minecraft.world.level.levelgen.GenerationStep.Decoration;
|
||||
import net.minecraft.world.level.levelgen.Noises;
|
||||
import net.minecraft.world.level.levelgen.SurfaceRules;
|
||||
import net.minecraft.world.level.levelgen.carver.ConfiguredWorldCarver;
|
||||
import net.minecraft.world.level.levelgen.placement.PlacedFeature;
|
||||
|
||||
import net.fabricmc.fabric.api.biome.v1.BiomeModifications;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import org.betterx.bclib.api.surface.SurfaceRuleBuilder;
|
||||
import org.betterx.bclib.entity.BCLEntityWrapper;
|
||||
import org.betterx.bclib.mixin.common.BiomeGenerationSettingsAccessor;
|
||||
import org.betterx.bclib.util.CollectionsUtil;
|
||||
import org.betterx.bclib.util.ColorUtil;
|
||||
import org.betterx.bclib.util.Pair;
|
||||
import org.betterx.bclib.util.TriFunction;
|
||||
import org.betterx.bclib.world.biomes.BCLBiome;
|
||||
import org.betterx.bclib.world.biomes.BCLBiomeSettings;
|
||||
import org.betterx.bclib.world.features.BCLFeature;
|
||||
import org.betterx.bclib.world.structures.BCLStructure;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class BCLBiomeBuilder {
|
||||
@FunctionalInterface
|
||||
public interface BiomeSupplier<T> extends TriFunction<ResourceLocation, Biome, BCLBiomeSettings, T> {
|
||||
}
|
||||
|
||||
private static final BCLBiomeBuilder INSTANCE = new BCLBiomeBuilder();
|
||||
private static final SurfaceRules.ConditionSource SURFACE_NOISE = SurfaceRules.noiseCondition(Noises.SOUL_SAND_LAYER,
|
||||
-0.012);
|
||||
|
||||
private final List<TagKey<Biome>> structureTags = new ArrayList<>(8);
|
||||
private final List<Pair<GenerationStep.Carving, Holder<? extends ConfiguredWorldCarver<?>>>> carvers = new ArrayList<>(1);
|
||||
private BiomeGenerationSettings.Builder generationSettings;
|
||||
private BiomeSpecialEffects.Builder effectsBuilder;
|
||||
private MobSpawnSettings.Builder spawnSettings;
|
||||
private SurfaceRules.RuleSource surfaceRule;
|
||||
private Precipitation precipitation;
|
||||
private ResourceLocation biomeID;
|
||||
|
||||
private final List<Climate.ParameterPoint> parameters = Lists.newArrayList();
|
||||
|
||||
//BiomeTags.IS_NETHER
|
||||
private float temperature;
|
||||
private float fogDensity;
|
||||
private float genChance;
|
||||
private float downfall;
|
||||
private float height;
|
||||
private int edgeSize;
|
||||
private BCLBiome edge;
|
||||
private boolean vertical;
|
||||
|
||||
|
||||
/**
|
||||
* Starts new biome building process.
|
||||
*
|
||||
* @param biomeID {@link ResourceLocation} biome identifier.
|
||||
* @return prepared {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public static BCLBiomeBuilder start(ResourceLocation biomeID) {
|
||||
INSTANCE.biomeID = biomeID;
|
||||
INSTANCE.precipitation = Precipitation.NONE;
|
||||
INSTANCE.generationSettings = null;
|
||||
INSTANCE.effectsBuilder = null;
|
||||
INSTANCE.spawnSettings = null;
|
||||
INSTANCE.structureTags.clear();
|
||||
INSTANCE.temperature = 1.0F;
|
||||
INSTANCE.fogDensity = 1.0F;
|
||||
INSTANCE.edgeSize = 0;
|
||||
INSTANCE.downfall = 1.0F;
|
||||
INSTANCE.genChance = 1.0F;
|
||||
INSTANCE.height = 0.1F;
|
||||
INSTANCE.vertical = false;
|
||||
INSTANCE.edge = null;
|
||||
INSTANCE.carvers.clear();
|
||||
INSTANCE.parameters.clear();
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public BCLBiomeBuilder addNetherClimateParamater(float temperature, float humidity) {
|
||||
parameters.add(Climate.parameters(temperature, humidity, 0, 0, 0, 0, 0));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set biome {@link Precipitation}. Affect biome visual effects (rain, snow, none).
|
||||
*
|
||||
* @param precipitation {@link Precipitation}
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder precipitation(Precipitation precipitation) {
|
||||
this.precipitation = precipitation;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set biome temperature, affect plant color, biome generation and ice formation.
|
||||
*
|
||||
* @param temperature biome temperature.
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder temperature(float temperature) {
|
||||
this.temperature = temperature;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set biome wetness (same as downfall). Affect plant color and biome generation.
|
||||
*
|
||||
* @param wetness biome wetness (downfall).
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder wetness(float wetness) {
|
||||
this.downfall = wetness;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds mob spawning to biome.
|
||||
*
|
||||
* @param entityType {@link EntityType} mob type.
|
||||
* @param weight spawn weight.
|
||||
* @param minGroupCount minimum mobs in group.
|
||||
* @param maxGroupCount maximum mobs in group.
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public <M extends Mob> BCLBiomeBuilder spawn(EntityType<M> entityType,
|
||||
int weight,
|
||||
int minGroupCount,
|
||||
int maxGroupCount) {
|
||||
getSpawns().addSpawn(entityType.getCategory(),
|
||||
new SpawnerData(entityType, weight, minGroupCount, maxGroupCount));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds mob spawning to biome.
|
||||
*
|
||||
* @param wrapper {@link BCLEntityWrapper} mob type.
|
||||
* @param weight spawn weight.
|
||||
* @param minGroupCount minimum mobs in group.
|
||||
* @param maxGroupCount maximum mobs in group.
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public <M extends Mob> BCLBiomeBuilder spawn(BCLEntityWrapper<M> wrapper,
|
||||
int weight,
|
||||
int minGroupCount,
|
||||
int maxGroupCount) {
|
||||
if (wrapper.canSpawn()) {
|
||||
return spawn(wrapper.type(), weight, minGroupCount, maxGroupCount);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds ambient particles to thr biome.
|
||||
*
|
||||
* @param particle {@link ParticleOptions} particles (or {@link net.minecraft.core.particles.ParticleType}).
|
||||
* @param probability particle spawn probability, should have low value (example: 0.01F).
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder particles(ParticleOptions particle, float probability) {
|
||||
getEffects().ambientParticle(new AmbientParticleSettings(particle, probability));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets sky color for the biome. Color is in ARGB int format.
|
||||
*
|
||||
* @param color ARGB color as integer.
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder skyColor(int color) {
|
||||
getEffects().skyColor(color);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets sky color for the biome. Color represented as red, green and blue channel values.
|
||||
*
|
||||
* @param red red color component [0-255]
|
||||
* @param green green color component [0-255]
|
||||
* @param blue blue color component [0-255]
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder skyColor(int red, int green, int blue) {
|
||||
red = Mth.clamp(red, 0, 255);
|
||||
green = Mth.clamp(green, 0, 255);
|
||||
blue = Mth.clamp(blue, 0, 255);
|
||||
return skyColor(ColorUtil.color(red, green, blue));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets fog color for the biome. Color is in ARGB int format.
|
||||
*
|
||||
* @param color ARGB color as integer.
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder fogColor(int color) {
|
||||
getEffects().fogColor(color);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets fog color for the biome. Color represented as red, green and blue channel values.
|
||||
*
|
||||
* @param red red color component [0-255]
|
||||
* @param green green color component [0-255]
|
||||
* @param blue blue color component [0-255]
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder fogColor(int red, int green, int blue) {
|
||||
red = Mth.clamp(red, 0, 255);
|
||||
green = Mth.clamp(green, 0, 255);
|
||||
blue = Mth.clamp(blue, 0, 255);
|
||||
return fogColor(ColorUtil.color(red, green, blue));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets fog density for the biome.
|
||||
*
|
||||
* @param density fog density as a float, default value is 1.0F.
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder fogDensity(float density) {
|
||||
this.fogDensity = density;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets generation chance for this biome.
|
||||
*
|
||||
* @param genChance
|
||||
* @return same {@link BCLBiomeBuilder}.
|
||||
*/
|
||||
public BCLBiomeBuilder genChance(float genChance) {
|
||||
this.genChance = genChance;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets edge size for this biome.
|
||||
*
|
||||
* @param edgeSize size of the Edge (in Blocks)
|
||||
* @return same {@link BCLBiomeBuilder}.
|
||||
*/
|
||||
public BCLBiomeBuilder edgeSize(int edgeSize) {
|
||||
this.edgeSize = edgeSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets edge-Biome for this biome.
|
||||
*
|
||||
* @param edge The Edge Biome
|
||||
* @return same {@link BCLBiomeBuilder}.
|
||||
*/
|
||||
public BCLBiomeBuilder edge(BCLBiome edge) {
|
||||
this.edge = edge;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets edge-Biome for this biome.
|
||||
*
|
||||
* @param edge The Edge Biome
|
||||
* @param edgeSize size of the Edge (in Blocks)
|
||||
* @return same {@link BCLBiomeBuilder}.
|
||||
*/
|
||||
public BCLBiomeBuilder edge(BCLBiome edge, int edgeSize) {
|
||||
this.edge(edge);
|
||||
this.edgeSize(edgeSize);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets water color for the biome. Color is in ARGB int format.
|
||||
*
|
||||
* @param color ARGB color as integer.
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder waterColor(int color) {
|
||||
getEffects().waterColor(color);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets water color for the biome. Color represented as red, green and blue channel values.
|
||||
*
|
||||
* @param red red color component [0-255]
|
||||
* @param green green color component [0-255]
|
||||
* @param blue blue color component [0-255]
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder waterColor(int red, int green, int blue) {
|
||||
red = Mth.clamp(red, 0, 255);
|
||||
green = Mth.clamp(green, 0, 255);
|
||||
blue = Mth.clamp(blue, 0, 255);
|
||||
return waterColor(ColorUtil.color(red, green, blue));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets underwater fog color for the biome. Color is in ARGB int format.
|
||||
*
|
||||
* @param color ARGB color as integer.
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder waterFogColor(int color) {
|
||||
getEffects().waterFogColor(color);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets underwater fog color for the biome. Color represented as red, green and blue channel values.
|
||||
*
|
||||
* @param red red color component [0-255]
|
||||
* @param green green color component [0-255]
|
||||
* @param blue blue color component [0-255]
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder waterFogColor(int red, int green, int blue) {
|
||||
red = Mth.clamp(red, 0, 255);
|
||||
green = Mth.clamp(green, 0, 255);
|
||||
blue = Mth.clamp(blue, 0, 255);
|
||||
return waterFogColor(ColorUtil.color(red, green, blue));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets water and underwater fig color for the biome. Color is in ARGB int format.
|
||||
*
|
||||
* @param color ARGB color as integer.
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder waterAndFogColor(int color) {
|
||||
return waterColor(color).waterFogColor(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets water and underwater fig color for the biome. Color is in ARGB int format.
|
||||
*
|
||||
* @param red red color component [0-255]
|
||||
* @param green green color component [0-255]
|
||||
* @param blue blue color component [0-255]
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder waterAndFogColor(int red, int green, int blue) {
|
||||
red = Mth.clamp(red, 0, 255);
|
||||
green = Mth.clamp(green, 0, 255);
|
||||
blue = Mth.clamp(blue, 0, 255);
|
||||
return waterAndFogColor(ColorUtil.color(red, green, blue));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets grass color for the biome. Color is in ARGB int format.
|
||||
*
|
||||
* @param color ARGB color as integer.
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder grassColor(int color) {
|
||||
getEffects().grassColorOverride(color);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets grass color for the biome. Color represented as red, green and blue channel values.
|
||||
*
|
||||
* @param red red color component [0-255]
|
||||
* @param green green color component [0-255]
|
||||
* @param blue blue color component [0-255]
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder grassColor(int red, int green, int blue) {
|
||||
red = Mth.clamp(red, 0, 255);
|
||||
green = Mth.clamp(green, 0, 255);
|
||||
blue = Mth.clamp(blue, 0, 255);
|
||||
return grassColor(ColorUtil.color(red, green, blue));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets leaves and plants color for the biome. Color is in ARGB int format.
|
||||
*
|
||||
* @param color ARGB color as integer.
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder foliageColor(int color) {
|
||||
getEffects().foliageColorOverride(color);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets leaves and plants color for the biome. Color represented as red, green and blue channel values.
|
||||
*
|
||||
* @param red red color component [0-255]
|
||||
* @param green green color component [0-255]
|
||||
* @param blue blue color component [0-255]
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder foliageColor(int red, int green, int blue) {
|
||||
red = Mth.clamp(red, 0, 255);
|
||||
green = Mth.clamp(green, 0, 255);
|
||||
blue = Mth.clamp(blue, 0, 255);
|
||||
return foliageColor(ColorUtil.color(red, green, blue));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets grass, leaves and all plants color for the biome. Color is in ARGB int format.
|
||||
*
|
||||
* @param color ARGB color as integer.
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder plantsColor(int color) {
|
||||
return grassColor(color).foliageColor(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets grass, leaves and all plants color for the biome. Color represented as red, green and blue channel values.
|
||||
*
|
||||
* @param red red color component [0-255]
|
||||
* @param green green color component [0-255]
|
||||
* @param blue blue color component [0-255]
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder plantsColor(int red, int green, int blue) {
|
||||
red = Mth.clamp(red, 0, 255);
|
||||
green = Mth.clamp(green, 0, 255);
|
||||
blue = Mth.clamp(blue, 0, 255);
|
||||
return plantsColor(ColorUtil.color(red, green, blue));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets biome music, used for biomes in the Nether and End.
|
||||
*
|
||||
* @param music {@link Music} to use.
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder music(Music music) {
|
||||
getEffects().backgroundMusic(music);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets biome music, used for biomes in the Nether and End.
|
||||
*
|
||||
* @param music {@link SoundEvent} to use.
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder music(SoundEvent music) {
|
||||
return music(new Music(music, 600, 2400, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets biome ambient loop sound. Can be used for biome environment.
|
||||
*
|
||||
* @param loopSound {@link SoundEvent} to use as a loop.
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder loop(SoundEvent loopSound) {
|
||||
getEffects().ambientLoopSound(loopSound);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets biome mood sound. Can be used for biome environment.
|
||||
*
|
||||
* @param mood {@link SoundEvent} to use as a mood.
|
||||
* @param tickDelay delay between sound events in ticks.
|
||||
* @param blockSearchExtent block search radius (for area available for sound).
|
||||
* @param soundPositionOffset offset in sound.
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder mood(SoundEvent mood, int tickDelay, int blockSearchExtent, float soundPositionOffset) {
|
||||
getEffects().ambientMoodSound(new AmbientMoodSettings(mood, tickDelay, blockSearchExtent, soundPositionOffset));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets biome mood sound. Can be used for biome environment.
|
||||
*
|
||||
* @param mood {@link SoundEvent} to use as a mood.
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder mood(SoundEvent mood) {
|
||||
return mood(mood, 6000, 8, 2.0F);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets biome additionsl ambient sounds.
|
||||
*
|
||||
* @param additions {@link SoundEvent} to use.
|
||||
* @param intensity sound intensity. Default is 0.0111F.
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder additions(SoundEvent additions, float intensity) {
|
||||
getEffects().ambientAdditionsSound(new AmbientAdditionsSettings(additions, intensity));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets biome additionsl ambient sounds.
|
||||
*
|
||||
* @param additions {@link SoundEvent} to use.
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder additions(SoundEvent additions) {
|
||||
return additions(additions, 0.0111F);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new feature to the biome.
|
||||
*
|
||||
* @param decoration {@link Decoration} feature step.
|
||||
* @param feature {@link PlacedFeature}.
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder feature(Decoration decoration, Holder<PlacedFeature> feature) {
|
||||
getGeneration().addFeature(decoration, feature);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds vanilla Mushrooms.
|
||||
*
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder defaultMushrooms() {
|
||||
return feature(BiomeDefaultFeatures::addDefaultMushrooms);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds vanilla Nether Ores.
|
||||
*
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder netherDefaultOres() {
|
||||
return feature(BiomeDefaultFeatures::addNetherDefaultOres);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will add features into biome, used for vanilla feature adding functions.
|
||||
*
|
||||
* @param featureAdd {@link Consumer} with {@link BiomeGenerationSettings.Builder}.
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder feature(Consumer<BiomeGenerationSettings.Builder> featureAdd) {
|
||||
featureAdd.accept(getGeneration());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new feature to the biome.
|
||||
*
|
||||
* @param feature {@link BCLFeature}.
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder feature(BCLFeature feature) {
|
||||
return feature(feature.getDecoration(), feature.getPlacedFeature());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new structure feature into the biome.
|
||||
*
|
||||
* @param structureTag {@link TagKey} to add.
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder structure(TagKey<Biome> structureTag) {
|
||||
structureTags.add(structureTag);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new structure feature into thr biome. Will add building biome into the structure list.
|
||||
*
|
||||
* @param structure {@link BCLStructure} to add.
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder structure(BCLStructure structure) {
|
||||
structure.addInternalBiome(biomeID);
|
||||
return structure(structure.biomeTag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new world carver into the biome.
|
||||
*
|
||||
* @param carver {@link ConfiguredWorldCarver} to add.
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder carver(GenerationStep.Carving step, Holder<? extends ConfiguredWorldCarver<?>> carver) {
|
||||
final ResourceLocation immutableID = biomeID;
|
||||
var oKey = carver.unwrapKey();
|
||||
if (oKey.isPresent()) {
|
||||
BiomeModifications.addCarver(ctx -> ctx.getBiomeKey().location().equals(immutableID),
|
||||
step,
|
||||
(ResourceKey<ConfiguredWorldCarver<?>>) oKey.get());
|
||||
}
|
||||
//carvers.add(new Pair<>(step, carver));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new world surface rule for the given block
|
||||
*
|
||||
* @param surfaceBlock {@link Block} to use.
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder surface(Block surfaceBlock) {
|
||||
return surface(surfaceBlock.defaultBlockState());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new world surface rule for the given block
|
||||
*
|
||||
* @param surfaceBlock {@link BlockState} to use.
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder surface(BlockState surfaceBlock) {
|
||||
return surface(SurfaceRuleBuilder.start().surface(surfaceBlock).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds blocks to the biome surface and below it (with specified depth).
|
||||
*
|
||||
* @param surfaceBlock {@link Block} that will cover biome.
|
||||
* @param subterrainBlock {@link Block} below it with specified depth.
|
||||
* @param depth thickness of bottom block layer.
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder surface(Block surfaceBlock, Block subterrainBlock, int depth) {
|
||||
return surface(SurfaceRuleBuilder
|
||||
.start()
|
||||
.surface(surfaceBlock.defaultBlockState())
|
||||
.subsurface(subterrainBlock.defaultBlockState(), depth)
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds surface rule to this biome.
|
||||
*
|
||||
* @param newSurfaceRule {link SurfaceRules.RuleSource} surface rule.
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder surface(SurfaceRules.RuleSource newSurfaceRule) {
|
||||
this.surfaceRule = newSurfaceRule;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set terrain height for the biome. Can be used in custom generators, doesn't change vanilla biome distribution or generation.
|
||||
*
|
||||
* @param height a relative float terrain height value.
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder terrainHeight(float height) {
|
||||
this.height = height;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make this a vertical Biome
|
||||
*
|
||||
* @return same {@link BCLBiomeBuilder} instance.
|
||||
*/
|
||||
public BCLBiomeBuilder vertical() {
|
||||
this.vertical = vertical;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalize biome creation.
|
||||
*
|
||||
* @return created {@link BCLBiome} instance.
|
||||
*/
|
||||
public BCLBiome build() {
|
||||
return build((BiomeSupplier<BCLBiome>) BCLBiome::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalize biome creation.
|
||||
*
|
||||
* @param biomeConstructor {@link BiFunction} biome constructor.
|
||||
* @return created {@link BCLBiome} instance.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public <T extends BCLBiome> T build(BiFunction<ResourceLocation, Biome, T> biomeConstructor) {
|
||||
return build((id, biome, settings) -> biomeConstructor.apply(id, biome));
|
||||
}
|
||||
|
||||
private static BiomeGenerationSettings fixGenerationSettings(BiomeGenerationSettings settings) {
|
||||
//Fabric Biome Modification API can not handle an empty carver map, thus we will create one with
|
||||
//an empty HolderSet for every possible step:
|
||||
//https://github.com/FabricMC/fabric/issues/2079
|
||||
//TODO: Remove, once fabric gets fixed
|
||||
if (settings instanceof BiomeGenerationSettingsAccessor acc) {
|
||||
Map<GenerationStep.Carving, HolderSet<ConfiguredWorldCarver<?>>> carvers = CollectionsUtil.getMutable(acc.bclib_getCarvers());
|
||||
for (GenerationStep.Carving step : GenerationStep.Carving.values()) {
|
||||
carvers.computeIfAbsent(step, __ -> HolderSet.direct(Lists.newArrayList()));
|
||||
}
|
||||
acc.bclib_setCarvers(carvers);
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalize biome creation.
|
||||
*
|
||||
* @param biomeConstructor {@link BiomeSupplier} biome constructor.
|
||||
* @return created {@link BCLBiome} instance.
|
||||
*/
|
||||
public <T extends BCLBiome> T build(BiomeSupplier<T> biomeConstructor) {
|
||||
BiomeBuilder builder = new BiomeBuilder()
|
||||
.precipitation(precipitation)
|
||||
.temperature(temperature)
|
||||
.downfall(downfall);
|
||||
|
||||
builder.mobSpawnSettings(getSpawns().build());
|
||||
builder.specialEffects(getEffects().build());
|
||||
|
||||
builder.generationSettings(fixGenerationSettings(getGeneration().build()));
|
||||
|
||||
BCLBiomeSettings settings = BCLBiomeSettings.createBCL()
|
||||
.setTerrainHeight(height)
|
||||
.setFogDensity(fogDensity)
|
||||
.setGenChance(genChance)
|
||||
.setEdgeSize(edgeSize)
|
||||
.setEdge(edge)
|
||||
.setVertical(vertical)
|
||||
.build();
|
||||
|
||||
final Biome biome = builder.build();
|
||||
final T res = biomeConstructor.apply(biomeID, biome, settings);
|
||||
res.attachStructures(structureTags);
|
||||
res.setSurface(surfaceRule);
|
||||
res.addClimateParameters(parameters);
|
||||
|
||||
//carvers.forEach(cfg -> BiomeAPI.addBiomeCarver(biome, cfg.second, cfg.first));
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create {@link BiomeSpecialEffects.Builder} for biome visual effects.
|
||||
* For internal usage only.
|
||||
* For internal usage only.
|
||||
*
|
||||
* @return new or same {@link BiomeSpecialEffects.Builder} instance.
|
||||
*/
|
||||
private BiomeSpecialEffects.Builder getEffects() {
|
||||
if (effectsBuilder == null) {
|
||||
effectsBuilder = new BiomeSpecialEffects.Builder();
|
||||
}
|
||||
return effectsBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create {@link MobSpawnSettings.Builder} for biome mob spawning.
|
||||
* For internal usage only.
|
||||
*
|
||||
* @return new or same {@link MobSpawnSettings.Builder} instance.
|
||||
*/
|
||||
private MobSpawnSettings.Builder getSpawns() {
|
||||
if (spawnSettings == null) {
|
||||
spawnSettings = new MobSpawnSettings.Builder();
|
||||
}
|
||||
return spawnSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create {@link BiomeGenerationSettings.Builder} for biome features and generation.
|
||||
* For internal usage only.
|
||||
*
|
||||
* @return new or same {@link BiomeGenerationSettings.Builder} instance.
|
||||
*/
|
||||
private BiomeGenerationSettings.Builder getGeneration() {
|
||||
if (generationSettings == null) {
|
||||
generationSettings = new BiomeGenerationSettings.Builder();
|
||||
}
|
||||
return generationSettings;
|
||||
}
|
||||
}
|
1049
src/main/java/org/betterx/bclib/api/biomes/BiomeAPI.java
Normal file
1049
src/main/java/org/betterx/bclib/api/biomes/BiomeAPI.java
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,109 @@
|
|||
package org.betterx.bclib.api.dataexchange;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ClientPacketListener;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.server.network.ServerGamePacketListenerImpl;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public abstract class BaseDataHandler {
|
||||
private final boolean originatesOnServer;
|
||||
@NotNull
|
||||
private final ResourceLocation identifier;
|
||||
|
||||
protected BaseDataHandler(ResourceLocation identifier, boolean originatesOnServer) {
|
||||
this.originatesOnServer = originatesOnServer;
|
||||
this.identifier = identifier;
|
||||
}
|
||||
|
||||
final public boolean getOriginatesOnServer() {
|
||||
return originatesOnServer;
|
||||
}
|
||||
|
||||
final public ResourceLocation getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
abstract void receiveFromServer(Minecraft client,
|
||||
ClientPacketListener handler,
|
||||
FriendlyByteBuf buf,
|
||||
PacketSender responseSender);
|
||||
|
||||
private ServerPlayer lastMessageSender;
|
||||
|
||||
void receiveFromClient(MinecraftServer server,
|
||||
ServerPlayer player,
|
||||
ServerGamePacketListenerImpl handler,
|
||||
FriendlyByteBuf buf,
|
||||
PacketSender responseSender) {
|
||||
lastMessageSender = player;
|
||||
}
|
||||
|
||||
final protected boolean reply(BaseDataHandler message, MinecraftServer server) {
|
||||
if (lastMessageSender == null) return false;
|
||||
message.sendToClient(server, lastMessageSender);
|
||||
return true;
|
||||
}
|
||||
|
||||
abstract void sendToClient(MinecraftServer server);
|
||||
|
||||
abstract void sendToClient(MinecraftServer server, ServerPlayer player);
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
abstract void sendToServer(Minecraft client);
|
||||
|
||||
protected boolean isBlocking() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BasDataHandler{" + "originatesOnServer=" + originatesOnServer + ", identifier=" + identifier + '}';
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a String to a buffer (Convenience Method)
|
||||
*
|
||||
* @param buf The buffer to write to
|
||||
* @param s The String you want to write
|
||||
*/
|
||||
public static void writeString(FriendlyByteBuf buf, String s) {
|
||||
buf.writeByteArray(s.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a string from a buffer (Convenience Method)
|
||||
*
|
||||
* @param buf Thea buffer to read from
|
||||
* @return The received String
|
||||
*/
|
||||
public static String readString(FriendlyByteBuf buf) {
|
||||
byte[] data = buf.readByteArray();
|
||||
return new String(data, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof BaseDataHandler)) return false;
|
||||
BaseDataHandler that = (BaseDataHandler) o;
|
||||
return originatesOnServer == that.originatesOnServer && identifier.equals(that.identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(originatesOnServer, identifier);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package org.betterx.bclib.api.dataexchange;
|
||||
|
||||
import org.betterx.bclib.api.dataexchange.handler.DataExchange;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
abstract class Connector {
|
||||
protected final DataExchange api;
|
||||
|
||||
Connector(DataExchange api) {
|
||||
this.api = api;
|
||||
}
|
||||
|
||||
public abstract boolean onClient();
|
||||
|
||||
protected Set<DataHandlerDescriptor> getDescriptors() {
|
||||
return api.getDescriptors();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
package org.betterx.bclib.api.dataexchange;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ClientPacketListener;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.api.dataexchange.handler.DataExchange;
|
||||
|
||||
/**
|
||||
* This is an internal class that handles a Clienetside players Connection to a Server
|
||||
*/
|
||||
@Environment(EnvType.CLIENT)
|
||||
public class ConnectorClientside extends Connector {
|
||||
private Minecraft client;
|
||||
|
||||
ConnectorClientside(DataExchange api) {
|
||||
super(api);
|
||||
this.client = null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onClient() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void onPlayInit(ClientPacketListener handler, Minecraft client) {
|
||||
if (this.client != null && this.client != client) {
|
||||
BCLib.LOGGER.warning("Client changed!");
|
||||
}
|
||||
this.client = client;
|
||||
for (DataHandlerDescriptor desc : getDescriptors()) {
|
||||
ClientPlayNetworking.registerReceiver(desc.IDENTIFIER, (_client, _handler, _buf, _responseSender) -> {
|
||||
receiveFromServer(desc, _client, _handler, _buf, _responseSender);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void onPlayReady(ClientPacketListener handler, PacketSender sender, Minecraft client) {
|
||||
for (DataHandlerDescriptor desc : getDescriptors()) {
|
||||
if (desc.sendOnJoin) {
|
||||
BaseDataHandler h = desc.JOIN_INSTANCE.get();
|
||||
if (!h.getOriginatesOnServer()) {
|
||||
h.sendToServer(client);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onPlayDisconnect(ClientPacketListener handler, Minecraft client) {
|
||||
for (DataHandlerDescriptor desc : getDescriptors()) {
|
||||
ClientPlayNetworking.unregisterReceiver(desc.IDENTIFIER);
|
||||
}
|
||||
}
|
||||
|
||||
void receiveFromServer(DataHandlerDescriptor desc,
|
||||
Minecraft client,
|
||||
ClientPacketListener handler,
|
||||
FriendlyByteBuf buf,
|
||||
PacketSender responseSender) {
|
||||
BaseDataHandler h = desc.INSTANCE.get();
|
||||
h.receiveFromServer(client, handler, buf, responseSender);
|
||||
}
|
||||
|
||||
public void sendToServer(BaseDataHandler h) {
|
||||
if (client == null) {
|
||||
throw new RuntimeException("[internal error] Client not initialized yet!");
|
||||
}
|
||||
h.sendToServer(this.client);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
package org.betterx.bclib.api.dataexchange;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.server.network.ServerGamePacketListenerImpl;
|
||||
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.api.dataexchange.handler.DataExchange;
|
||||
|
||||
/**
|
||||
* This is an internal class that handles a Serverside Connection to a Client-Player
|
||||
*/
|
||||
public class ConnectorServerside extends Connector {
|
||||
private MinecraftServer server;
|
||||
|
||||
ConnectorServerside(DataExchange api) {
|
||||
super(api);
|
||||
server = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onClient() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void onPlayInit(ServerGamePacketListenerImpl handler, MinecraftServer server) {
|
||||
if (this.server != null && this.server != server) {
|
||||
BCLib.LOGGER.warning("Server changed!");
|
||||
}
|
||||
this.server = server;
|
||||
for (DataHandlerDescriptor desc : getDescriptors()) {
|
||||
ServerPlayNetworking.registerReceiver(handler,
|
||||
desc.IDENTIFIER,
|
||||
(_server, _player, _handler, _buf, _responseSender) -> {
|
||||
receiveFromClient(desc,
|
||||
_server,
|
||||
_player,
|
||||
_handler,
|
||||
_buf,
|
||||
_responseSender);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void onPlayReady(ServerGamePacketListenerImpl handler, PacketSender sender, MinecraftServer server) {
|
||||
for (DataHandlerDescriptor desc : getDescriptors()) {
|
||||
if (desc.sendOnJoin) {
|
||||
BaseDataHandler h = desc.JOIN_INSTANCE.get();
|
||||
if (h.getOriginatesOnServer()) {
|
||||
h.sendToClient(server, handler.player);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onPlayDisconnect(ServerGamePacketListenerImpl handler, MinecraftServer server) {
|
||||
for (DataHandlerDescriptor desc : getDescriptors()) {
|
||||
ServerPlayNetworking.unregisterReceiver(handler, desc.IDENTIFIER);
|
||||
}
|
||||
}
|
||||
|
||||
void receiveFromClient(DataHandlerDescriptor desc,
|
||||
MinecraftServer server,
|
||||
ServerPlayer player,
|
||||
ServerGamePacketListenerImpl handler,
|
||||
FriendlyByteBuf buf,
|
||||
PacketSender responseSender) {
|
||||
BaseDataHandler h = desc.INSTANCE.get();
|
||||
h.receiveFromClient(server, player, handler, buf, responseSender);
|
||||
}
|
||||
|
||||
public void sendToClient(BaseDataHandler h) {
|
||||
if (server == null) {
|
||||
throw new RuntimeException("[internal error] Server not initialized yet!");
|
||||
}
|
||||
h.sendToClient(this.server);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,214 @@
|
|||
package org.betterx.bclib.api.dataexchange;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.api.dataexchange.handler.DataExchange;
|
||||
import org.betterx.bclib.api.dataexchange.handler.autosync.AutoSync;
|
||||
import org.betterx.bclib.api.dataexchange.handler.autosync.AutoSyncID;
|
||||
import org.betterx.bclib.config.Config;
|
||||
import org.betterx.bclib.util.ModUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
public class DataExchangeAPI extends DataExchange {
|
||||
private final static List<String> MODS = Lists.newArrayList();
|
||||
|
||||
/**
|
||||
* You should never need to create a custom instance of this Object.
|
||||
*/
|
||||
public DataExchangeAPI() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
protected ConnectorClientside clientSupplier(DataExchange api) {
|
||||
return new ConnectorClientside(api);
|
||||
}
|
||||
|
||||
protected ConnectorServerside serverSupplier(DataExchange api) {
|
||||
return new ConnectorServerside(api);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a mod to participate in the DataExchange.
|
||||
*
|
||||
* @param modID - {@link String} modID.
|
||||
*/
|
||||
public static void registerMod(String modID) {
|
||||
if (!MODS.contains(modID)) MODS.add(modID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a mod dependency to participate in the DataExchange.
|
||||
*
|
||||
* @param modID - {@link String} modID.
|
||||
*/
|
||||
public static void registerModDependency(String modID) {
|
||||
if (ModUtil.getModInfo(modID, false) != null && !"0.0.0".equals(ModUtil.getModVersion(modID))) {
|
||||
registerMod(modID);
|
||||
} else {
|
||||
BCLib.LOGGER.info("Mod Dependency '" + modID + "' not found. This is probably OK.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the IDs of all registered Mods.
|
||||
*
|
||||
* @return List of modIDs
|
||||
*/
|
||||
public static List<String> registeredMods() {
|
||||
return MODS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new Descriptor for a {@link DataHandler}.
|
||||
*
|
||||
* @param desc The Descriptor you want to add.
|
||||
*/
|
||||
public static void registerDescriptor(DataHandlerDescriptor desc) {
|
||||
DataExchange api = DataExchange.getInstance();
|
||||
api.getDescriptors()
|
||||
.add(desc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk-Add a Descriptors for your {@link DataHandler}-Objects.
|
||||
*
|
||||
* @param desc The Descriptors you want to add.
|
||||
*/
|
||||
public static void registerDescriptors(List<DataHandlerDescriptor> desc) {
|
||||
DataExchange api = DataExchange.getInstance();
|
||||
api.getDescriptors()
|
||||
.addAll(desc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the Handler.
|
||||
* <p>
|
||||
* Depending on what the result of {@link DataHandler#getOriginatesOnServer()}, the Data is sent from the server
|
||||
* to the client (if {@code true}) or the other way around.
|
||||
* <p>
|
||||
* The method {@link DataHandler#serializeData(FriendlyByteBuf, boolean)} is called just before the data is sent. You should
|
||||
* use this method to add the Data you need to the communication.
|
||||
*
|
||||
* @param h The Data that you want to send
|
||||
*/
|
||||
public static void send(BaseDataHandler h) {
|
||||
if (h.getOriginatesOnServer()) {
|
||||
DataExchangeAPI.getInstance().server.sendToClient(h);
|
||||
} else {
|
||||
DataExchangeAPI.getInstance().client.sendToServer(h);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a File for automatic client syncing.
|
||||
*
|
||||
* @param modID The ID of the calling Mod
|
||||
* @param fileName The name of the File
|
||||
*/
|
||||
public static void addAutoSyncFile(String modID, File fileName) {
|
||||
AutoSync.addAutoSyncFileData(modID, fileName, false, SyncFileHash.NEED_TRANSFER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a File for automatic client syncing.
|
||||
* <p>
|
||||
* The file is synced of the {@link SyncFileHash} on client and server are not equal. This method will not copy the
|
||||
* configs content from the client to the server.
|
||||
*
|
||||
* @param modID The ID of the calling Mod
|
||||
* @param uniqueID A unique Identifier for the File. (see {@link SyncFileHash#uniqueID} for
|
||||
* Details
|
||||
* @param fileName The name of the File
|
||||
*/
|
||||
public static void addAutoSyncFile(String modID, String uniqueID, File fileName) {
|
||||
AutoSync.addAutoSyncFileData(modID, uniqueID, fileName, false, SyncFileHash.NEED_TRANSFER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a File for automatic client syncing.
|
||||
* <p>
|
||||
* The content of the file is requested for comparison. This will copy the
|
||||
* entire file from the client to the server.
|
||||
* <p>
|
||||
* You should only use this option, if you need to compare parts of the file in order to decide
|
||||
* if the File needs to be copied. Normally using the {@link SyncFileHash}
|
||||
* for comparison is sufficient.
|
||||
*
|
||||
* @param modID The ID of the calling Mod
|
||||
* @param fileName The name of the File
|
||||
* @param needTransfer If the predicate returns true, the file needs to get copied to the server.
|
||||
*/
|
||||
public static void addAutoSyncFile(String modID, File fileName, AutoSync.NeedTransferPredicate needTransfer) {
|
||||
AutoSync.addAutoSyncFileData(modID, fileName, true, needTransfer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a File for automatic client syncing.
|
||||
* <p>
|
||||
* The content of the file is requested for comparison. This will copy the
|
||||
* entire file from the client to the server.
|
||||
* <p>
|
||||
* You should only use this option, if you need to compare parts of the file in order to decide
|
||||
* if the File needs to be copied. Normally using the {@link SyncFileHash}
|
||||
* for comparison is sufficient.
|
||||
*
|
||||
* @param modID The ID of the calling Mod
|
||||
* @param uniqueID A unique Identifier for the File. (see {@link SyncFileHash#uniqueID} for
|
||||
* Details
|
||||
* @param fileName The name of the File
|
||||
* @param needTransfer If the predicate returns true, the file needs to get copied to the server.
|
||||
*/
|
||||
public static void addAutoSyncFile(String modID,
|
||||
String uniqueID,
|
||||
File fileName,
|
||||
AutoSync.NeedTransferPredicate needTransfer) {
|
||||
AutoSync.addAutoSyncFileData(modID, uniqueID, fileName, true, needTransfer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a function that is called whenever the client receives a file from the server and replaced toe local
|
||||
* file with the new content.
|
||||
* <p>
|
||||
* This callback is usefull if you need to reload the new content before the game is quit.
|
||||
*
|
||||
* @param callback A Function that receives the AutoSyncID as well as the Filename.
|
||||
*/
|
||||
public static void addOnWriteCallback(BiConsumer<AutoSyncID, File> callback) {
|
||||
AutoSync.addOnWriteCallback(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sync-folder for a given Mod.
|
||||
* <p>
|
||||
* BCLib will ensure that the contents of sync-folder on the client is the same as the one on the server.
|
||||
*
|
||||
* @param modID ID of the Mod
|
||||
* @return The path to the sync-folder
|
||||
*/
|
||||
public static File getModSyncFolder(String modID) {
|
||||
File fl = AutoSync.SYNC_FOLDER.localFolder.resolve(modID.replace(".", "-")
|
||||
.replace(":", "-")
|
||||
.replace("\\", "-")
|
||||
.replace("/", "-"))
|
||||
.normalize()
|
||||
.toFile();
|
||||
|
||||
if (!fl.exists()) {
|
||||
fl.mkdirs();
|
||||
}
|
||||
return fl;
|
||||
}
|
||||
|
||||
static {
|
||||
addOnWriteCallback(Config::reloadSyncedConfig);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,315 @@
|
|||
package org.betterx.bclib.api.dataexchange;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ClientPacketListener;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.server.network.ServerGamePacketListenerImpl;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||
import net.fabricmc.fabric.api.networking.v1.PlayerLookup;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.api.dataexchange.handler.autosync.Chunker;
|
||||
import org.betterx.bclib.api.dataexchange.handler.autosync.Chunker.PacketChunkSender;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class DataHandler extends BaseDataHandler {
|
||||
public abstract static class WithoutPayload extends DataHandler {
|
||||
protected WithoutPayload(ResourceLocation identifier, boolean originatesOnServer) {
|
||||
super(identifier, originatesOnServer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean prepareData(boolean isClient) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void serializeData(FriendlyByteBuf buf, boolean isClient) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deserializeIncomingData(FriendlyByteBuf buf, PacketSender responseSender, boolean isClient) {
|
||||
}
|
||||
}
|
||||
|
||||
protected DataHandler(ResourceLocation identifier, boolean originatesOnServer) {
|
||||
super(identifier, originatesOnServer);
|
||||
}
|
||||
|
||||
protected boolean prepareData(boolean isClient) {
|
||||
return true;
|
||||
}
|
||||
|
||||
abstract protected void serializeData(FriendlyByteBuf buf, boolean isClient);
|
||||
|
||||
abstract protected void deserializeIncomingData(FriendlyByteBuf buf, PacketSender responseSender, boolean isClient);
|
||||
|
||||
abstract protected void runOnGameThread(Minecraft client, MinecraftServer server, boolean isClient);
|
||||
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
@Override
|
||||
void receiveFromServer(Minecraft client,
|
||||
ClientPacketListener handler,
|
||||
FriendlyByteBuf buf,
|
||||
PacketSender responseSender) {
|
||||
deserializeIncomingData(buf, responseSender, true);
|
||||
final Runnable runner = () -> runOnGameThread(client, null, true);
|
||||
|
||||
if (isBlocking()) client.executeBlocking(runner);
|
||||
else client.execute(runner);
|
||||
}
|
||||
|
||||
@Override
|
||||
void receiveFromClient(MinecraftServer server,
|
||||
ServerPlayer player,
|
||||
ServerGamePacketListenerImpl handler,
|
||||
FriendlyByteBuf buf,
|
||||
PacketSender responseSender) {
|
||||
super.receiveFromClient(server, player, handler, buf, responseSender);
|
||||
|
||||
deserializeIncomingData(buf, responseSender, false);
|
||||
final Runnable runner = () -> runOnGameThread(null, server, false);
|
||||
|
||||
if (isBlocking()) server.executeBlocking(runner);
|
||||
else server.execute(runner);
|
||||
}
|
||||
|
||||
@Override
|
||||
void sendToClient(MinecraftServer server) {
|
||||
if (prepareData(false)) {
|
||||
FriendlyByteBuf buf = PacketByteBufs.create();
|
||||
serializeData(buf, false);
|
||||
|
||||
_sendToClient(getIdentifier(), server, PlayerLookup.all(server), buf);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void sendToClient(MinecraftServer server, ServerPlayer player) {
|
||||
if (prepareData(false)) {
|
||||
FriendlyByteBuf buf = PacketByteBufs.create();
|
||||
serializeData(buf, false);
|
||||
|
||||
_sendToClient(getIdentifier(), server, List.of(player), buf);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void _sendToClient(ResourceLocation identifier,
|
||||
MinecraftServer server,
|
||||
Collection<ServerPlayer> players,
|
||||
FriendlyByteBuf buf) {
|
||||
if (buf.readableBytes() > Chunker.MAX_PACKET_SIZE) {
|
||||
final PacketChunkSender sender = new PacketChunkSender(buf, identifier);
|
||||
sender.sendChunks(players);
|
||||
} else {
|
||||
for (ServerPlayer player : players) {
|
||||
ServerPlayNetworking.send(player, identifier, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
@Override
|
||||
void sendToServer(Minecraft client) {
|
||||
if (prepareData(true)) {
|
||||
FriendlyByteBuf buf = PacketByteBufs.create();
|
||||
serializeData(buf, true);
|
||||
ClientPlayNetworking.send(getIdentifier(), buf);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Message that always originates on the Client
|
||||
*/
|
||||
public abstract static class FromClient extends BaseDataHandler {
|
||||
public abstract static class WithoutPayload extends FromClient {
|
||||
protected WithoutPayload(ResourceLocation identifier) {
|
||||
super(identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean prepareDataOnClient() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void serializeDataOnClient(FriendlyByteBuf buf) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deserializeIncomingDataOnServer(FriendlyByteBuf buf,
|
||||
Player player,
|
||||
PacketSender responseSender) {
|
||||
}
|
||||
}
|
||||
|
||||
protected FromClient(ResourceLocation identifier) {
|
||||
super(identifier, false);
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
protected boolean prepareDataOnClient() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
abstract protected void serializeDataOnClient(FriendlyByteBuf buf);
|
||||
|
||||
protected abstract void deserializeIncomingDataOnServer(FriendlyByteBuf buf,
|
||||
Player player,
|
||||
PacketSender responseSender);
|
||||
protected abstract void runOnServerGameThread(MinecraftServer server, Player player);
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
@Override
|
||||
void receiveFromServer(Minecraft client,
|
||||
ClientPacketListener handler,
|
||||
FriendlyByteBuf buf,
|
||||
PacketSender responseSender) {
|
||||
BCLib.LOGGER.error("[Internal Error] The message '" + getIdentifier() + "' must originate from the client!");
|
||||
}
|
||||
|
||||
@Override
|
||||
void receiveFromClient(MinecraftServer server,
|
||||
ServerPlayer player,
|
||||
ServerGamePacketListenerImpl handler,
|
||||
FriendlyByteBuf buf,
|
||||
PacketSender responseSender) {
|
||||
super.receiveFromClient(server, player, handler, buf, responseSender);
|
||||
|
||||
deserializeIncomingDataOnServer(buf, player, responseSender);
|
||||
final Runnable runner = () -> runOnServerGameThread(server, player);
|
||||
|
||||
if (isBlocking()) server.executeBlocking(runner);
|
||||
else server.execute(runner);
|
||||
}
|
||||
|
||||
@Override
|
||||
void sendToClient(MinecraftServer server) {
|
||||
BCLib.LOGGER.error("[Internal Error] The message '" + getIdentifier() + "' must originate from the client!");
|
||||
}
|
||||
|
||||
@Override
|
||||
void sendToClient(MinecraftServer server, ServerPlayer player) {
|
||||
BCLib.LOGGER.error("[Internal Error] The message '" + getIdentifier() + "' must originate from the client!");
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
@Override
|
||||
void sendToServer(Minecraft client) {
|
||||
if (prepareDataOnClient()) {
|
||||
FriendlyByteBuf buf = PacketByteBufs.create();
|
||||
serializeDataOnClient(buf);
|
||||
ClientPlayNetworking.send(getIdentifier(), buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Message that always originates on the Server
|
||||
*/
|
||||
public abstract static class FromServer extends BaseDataHandler {
|
||||
public abstract static class WithoutPayload extends FromServer {
|
||||
protected WithoutPayload(ResourceLocation identifier) {
|
||||
super(identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean prepareDataOnServer() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void serializeDataOnServer(FriendlyByteBuf buf) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deserializeIncomingDataOnClient(FriendlyByteBuf buf, PacketSender responseSender) {
|
||||
}
|
||||
}
|
||||
|
||||
protected FromServer(ResourceLocation identifier) {
|
||||
super(identifier, true);
|
||||
}
|
||||
|
||||
protected boolean prepareDataOnServer() {
|
||||
return true;
|
||||
}
|
||||
|
||||
abstract protected void serializeDataOnServer(FriendlyByteBuf buf);
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
abstract protected void deserializeIncomingDataOnClient(FriendlyByteBuf buf, PacketSender responseSender);
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
abstract protected void runOnClientGameThread(Minecraft client);
|
||||
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
@Override
|
||||
final void receiveFromServer(Minecraft client,
|
||||
ClientPacketListener handler,
|
||||
FriendlyByteBuf buf,
|
||||
PacketSender responseSender) {
|
||||
deserializeIncomingDataOnClient(buf, responseSender);
|
||||
final Runnable runner = () -> runOnClientGameThread(client);
|
||||
|
||||
if (isBlocking()) client.executeBlocking(runner);
|
||||
else client.execute(runner);
|
||||
}
|
||||
|
||||
@Override
|
||||
final void receiveFromClient(MinecraftServer server,
|
||||
ServerPlayer player,
|
||||
ServerGamePacketListenerImpl handler,
|
||||
FriendlyByteBuf buf,
|
||||
PacketSender responseSender) {
|
||||
super.receiveFromClient(server, player, handler, buf, responseSender);
|
||||
BCLib.LOGGER.error("[Internal Error] The message '" + getIdentifier() + "' must originate from the server!");
|
||||
}
|
||||
|
||||
public void receiveFromMemory(FriendlyByteBuf buf) {
|
||||
receiveFromServer(Minecraft.getInstance(), null, buf, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
final void sendToClient(MinecraftServer server) {
|
||||
if (prepareDataOnServer()) {
|
||||
FriendlyByteBuf buf = PacketByteBufs.create();
|
||||
serializeDataOnServer(buf);
|
||||
|
||||
_sendToClient(getIdentifier(), server, PlayerLookup.all(server), buf);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
final void sendToClient(MinecraftServer server, ServerPlayer player) {
|
||||
if (prepareDataOnServer()) {
|
||||
FriendlyByteBuf buf = PacketByteBufs.create();
|
||||
serializeDataOnServer(buf);
|
||||
|
||||
_sendToClient(getIdentifier(), server, List.of(player), buf);
|
||||
}
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
@Override
|
||||
final void sendToServer(Minecraft client) {
|
||||
BCLib.LOGGER.error("[Internal Error] The message '" + getIdentifier() + "' must originate from the server!");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package org.betterx.bclib.api.dataexchange;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class DataHandlerDescriptor {
|
||||
public DataHandlerDescriptor(@NotNull ResourceLocation identifier, @NotNull Supplier<BaseDataHandler> instancer) {
|
||||
this(identifier, instancer, instancer, false, false);
|
||||
}
|
||||
|
||||
public DataHandlerDescriptor(@NotNull ResourceLocation identifier,
|
||||
@NotNull Supplier<BaseDataHandler> instancer,
|
||||
boolean sendOnJoin,
|
||||
boolean sendBeforeEnter) {
|
||||
this(identifier, instancer, instancer, sendOnJoin, sendBeforeEnter);
|
||||
}
|
||||
|
||||
public DataHandlerDescriptor(@NotNull ResourceLocation identifier,
|
||||
@NotNull Supplier<BaseDataHandler> receiv_instancer,
|
||||
@NotNull Supplier<BaseDataHandler> join_instancer,
|
||||
boolean sendOnJoin,
|
||||
boolean sendBeforeEnter) {
|
||||
this.INSTANCE = receiv_instancer;
|
||||
this.JOIN_INSTANCE = join_instancer;
|
||||
this.IDENTIFIER = identifier;
|
||||
|
||||
this.sendOnJoin = sendOnJoin;
|
||||
this.sendBeforeEnter = sendBeforeEnter;
|
||||
}
|
||||
|
||||
public final boolean sendOnJoin;
|
||||
public final boolean sendBeforeEnter;
|
||||
@NotNull
|
||||
public final ResourceLocation IDENTIFIER;
|
||||
@NotNull
|
||||
public final Supplier<BaseDataHandler> INSTANCE;
|
||||
@NotNull
|
||||
public final Supplier<BaseDataHandler> JOIN_INSTANCE;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o instanceof ResourceLocation) {
|
||||
return o.equals(IDENTIFIER);
|
||||
}
|
||||
if (!(o instanceof DataHandlerDescriptor that)) return false;
|
||||
return IDENTIFIER.equals(that.IDENTIFIER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(IDENTIFIER);
|
||||
}
|
||||
}
|
160
src/main/java/org/betterx/bclib/api/dataexchange/FileHash.java
Normal file
160
src/main/java/org/betterx/bclib/api/dataexchange/FileHash.java
Normal file
|
@ -0,0 +1,160 @@
|
|||
package org.betterx.bclib.api.dataexchange;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class FileHash {
|
||||
private static final int ERR_DOES_NOT_EXIST = -10;
|
||||
private static final int ERR_IO_ERROR = -20;
|
||||
|
||||
/**
|
||||
* The md5-hash of the file
|
||||
*/
|
||||
@NotNull
|
||||
public final byte[] md5;
|
||||
|
||||
/**
|
||||
* The size (in bytes) of the input.
|
||||
*/
|
||||
public final int size;
|
||||
|
||||
/**
|
||||
* a value that is directly calculated from defined byte positions.
|
||||
*/
|
||||
public final int value;
|
||||
|
||||
FileHash(byte[] md5, int size, int value) {
|
||||
Objects.nonNull(md5);
|
||||
|
||||
this.md5 = md5;
|
||||
this.size = size;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
static FileHash createForEmpty(int errCode) {
|
||||
return new FileHash(new byte[0], 0, errCode);
|
||||
}
|
||||
|
||||
public boolean noFile() {
|
||||
return md5.length == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the Object to a buffer
|
||||
*
|
||||
* @param buf The buffer to write to
|
||||
*/
|
||||
public void serialize(FriendlyByteBuf buf) {
|
||||
buf.writeInt(size);
|
||||
buf.writeInt(value);
|
||||
buf.writeByteArray(md5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a Buffer to a new {@link SyncFileHash}-Object
|
||||
*
|
||||
* @param buf Thea buffer to read from
|
||||
* @return The received String
|
||||
*/
|
||||
public static FileHash deserialize(FriendlyByteBuf buf) {
|
||||
final int size = buf.readInt();
|
||||
final int value = buf.readInt();
|
||||
final byte[] md5 = buf.readByteArray();
|
||||
|
||||
return new FileHash(md5, size, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the md5-hash to a human readable string
|
||||
*
|
||||
* @return The converted String
|
||||
*/
|
||||
public String getMd5String() {
|
||||
return toHexString(md5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a byte-array to a hex-string representation
|
||||
*
|
||||
* @param bytes The source array
|
||||
* @return The resulting string, or an empty String if the input was {@code null}
|
||||
*/
|
||||
public static String toHexString(byte[] bytes) {
|
||||
if (bytes == null) return "";
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : bytes) {
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link FileHash}.
|
||||
*
|
||||
* @param file The input file
|
||||
* @return A new Instance. You can compare instances using {@link #equals(Object)} to determine if two files are
|
||||
* identical. Will return {@code null} when an error occurs or the File does not exist
|
||||
*/
|
||||
public static FileHash create(File file) {
|
||||
if (!file.exists()) return createForEmpty(ERR_DOES_NOT_EXIST);
|
||||
final Path path = file.toPath();
|
||||
|
||||
int size = 0;
|
||||
byte[] md5 = new byte[0];
|
||||
int value = 0;
|
||||
|
||||
try {
|
||||
byte[] data = Files.readAllBytes(path);
|
||||
|
||||
size = data.length;
|
||||
|
||||
value = size > 0 ? (data[size / 3] | (data[size / 2] << 8) | (data[size / 5] << 16)) : -1;
|
||||
if (size > 20) value |= data[20] << 24;
|
||||
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
md.update(data);
|
||||
md5 = md.digest();
|
||||
|
||||
return new FileHash(md5, size, value);
|
||||
} catch (IOException e) {
|
||||
BCLib.LOGGER.error("Failed to read file: " + file);
|
||||
return null;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
BCLib.LOGGER.error("Unable to build hash for file: " + file);
|
||||
}
|
||||
|
||||
return createForEmpty(ERR_IO_ERROR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof FileHash)) return false;
|
||||
FileHash fileHash = (FileHash) o;
|
||||
return size == fileHash.size && value == fileHash.value && Arrays.equals(md5, fileHash.md5);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = Objects.hash(size, value);
|
||||
result = 31 * result + Arrays.hashCode(md5);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%08x", size) + "-" + String.format("%08x", value) + "-" + getMd5String();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
package org.betterx.bclib.api.dataexchange;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
|
||||
import org.betterx.bclib.api.dataexchange.handler.autosync.AutoSync;
|
||||
import org.betterx.bclib.api.dataexchange.handler.autosync.AutoSyncID;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Calculates a hash based on the contents of a File.
|
||||
* <p>
|
||||
* A File-Hash contains the md5-sum of the File, as well as its size and byte-values from defined positions
|
||||
* <p>
|
||||
* You can compare instances using {@link #equals(Object)} to determine if two files are
|
||||
* identical.
|
||||
*/
|
||||
public class SyncFileHash extends AutoSyncID {
|
||||
public final FileHash hash;
|
||||
|
||||
SyncFileHash(String modID, File file, byte[] md5, int size, int value) {
|
||||
this(modID, file.getName(), md5, size, value);
|
||||
}
|
||||
|
||||
SyncFileHash(String modID, String uniqueID, byte[] md5, int size, int value) {
|
||||
this(modID, uniqueID, new FileHash(md5, size, value));
|
||||
}
|
||||
|
||||
SyncFileHash(String modID, File file, FileHash hash) {
|
||||
this(modID, file.getName(), hash);
|
||||
}
|
||||
|
||||
SyncFileHash(String modID, String uniqueID, FileHash hash) {
|
||||
super(modID, uniqueID);
|
||||
this.hash = hash;
|
||||
}
|
||||
|
||||
|
||||
final static AutoSync.NeedTransferPredicate NEED_TRANSFER = (clientHash, serverHash, content) -> !clientHash.equals(
|
||||
serverHash);
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + ": " + hash.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof SyncFileHash)) return false;
|
||||
if (!super.equals(o)) return false;
|
||||
SyncFileHash that = (SyncFileHash) o;
|
||||
return hash.equals(that.hash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(super.hashCode(), hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the Object to a buffer
|
||||
*
|
||||
* @param buf The buffer to write to
|
||||
*/
|
||||
public void serialize(FriendlyByteBuf buf) {
|
||||
hash.serialize(buf);
|
||||
DataHandler.writeString(buf, modID);
|
||||
DataHandler.writeString(buf, uniqueID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a Buffer to a new {@link SyncFileHash}-Object
|
||||
*
|
||||
* @param buf Thea buffer to read from
|
||||
* @return The received String
|
||||
*/
|
||||
public static SyncFileHash deserialize(FriendlyByteBuf buf) {
|
||||
final FileHash hash = FileHash.deserialize(buf);
|
||||
final String modID = DataHandler.readString(buf);
|
||||
final String uniqueID = DataHandler.readString(buf);
|
||||
|
||||
return new SyncFileHash(modID, uniqueID, hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link SyncFileHash}.
|
||||
* <p>
|
||||
* Will call {@link #create(String, File, String)} using the name of the File as {@code uniqueID}.
|
||||
*
|
||||
* @param modID ID of the calling Mod
|
||||
* @param file The input file
|
||||
* @return A new Instance. You can compare instances using {@link #equals(Object)} to determine if two files are
|
||||
* identical. Will return {@code null} when an error occurs or the File does not exist
|
||||
*/
|
||||
public static SyncFileHash create(String modID, File file) {
|
||||
return create(modID, file, file.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link SyncFileHash}.
|
||||
*
|
||||
* @param modID ID of the calling Mod
|
||||
* @param file The input file
|
||||
* @param uniqueID The unique ID that is used for this File (see {@link SyncFileHash#uniqueID} for Details.
|
||||
* @return A new Instance. You can compare instances using {@link #equals(Object)} to determine if two files are
|
||||
* identical. Will return {@code null} when an error occurs or the File does not exist
|
||||
*/
|
||||
public static SyncFileHash create(String modID, File file, String uniqueID) {
|
||||
return new SyncFileHash(modID, uniqueID, FileHash.create(file));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
package org.betterx.bclib.api.dataexchange.handler;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
|
||||
|
||||
import org.betterx.bclib.api.dataexchange.*;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
abstract public class DataExchange {
|
||||
|
||||
|
||||
private static DataExchangeAPI instance;
|
||||
|
||||
protected static DataExchangeAPI getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new DataExchangeAPI();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
protected ConnectorServerside server;
|
||||
protected ConnectorClientside client;
|
||||
protected final Set<DataHandlerDescriptor> descriptors;
|
||||
|
||||
|
||||
private final boolean didLoadSyncFolder = false;
|
||||
|
||||
abstract protected ConnectorClientside clientSupplier(DataExchange api);
|
||||
|
||||
abstract protected ConnectorServerside serverSupplier(DataExchange api);
|
||||
|
||||
protected DataExchange() {
|
||||
descriptors = new HashSet<>();
|
||||
}
|
||||
|
||||
public Set<DataHandlerDescriptor> getDescriptors() {
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
public static DataHandlerDescriptor getDescriptor(ResourceLocation identifier) {
|
||||
return getInstance().descriptors.stream().filter(d -> d.equals(identifier)).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
protected void initClientside() {
|
||||
if (client != null) return;
|
||||
client = clientSupplier(this);
|
||||
|
||||
ClientPlayConnectionEvents.INIT.register(client::onPlayInit);
|
||||
ClientPlayConnectionEvents.JOIN.register(client::onPlayReady);
|
||||
ClientPlayConnectionEvents.DISCONNECT.register(client::onPlayDisconnect);
|
||||
}
|
||||
|
||||
protected void initServerSide() {
|
||||
if (server != null) return;
|
||||
server = serverSupplier(this);
|
||||
|
||||
ServerPlayConnectionEvents.INIT.register(server::onPlayInit);
|
||||
ServerPlayConnectionEvents.JOIN.register(server::onPlayReady);
|
||||
ServerPlayConnectionEvents.DISCONNECT.register(server::onPlayDisconnect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all datastructures that need to exist in the client component.
|
||||
* <p>
|
||||
* This is automatically called by BCLib. You can register {@link DataHandler}-Objects before this Method is called
|
||||
*/
|
||||
@Environment(EnvType.CLIENT)
|
||||
public static void prepareClientside() {
|
||||
DataExchange api = DataExchange.getInstance();
|
||||
api.initClientside();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all datastructures that need to exist in the server component.
|
||||
* <p>
|
||||
* This is automatically called by BCLib. You can register {@link DataHandler}-Objects before this Method is called
|
||||
*/
|
||||
public static void prepareServerside() {
|
||||
DataExchange api = DataExchange.getInstance();
|
||||
api.initServerSide();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Automatically called before the player enters the world.
|
||||
* <p>
|
||||
* This is automatically called by BCLib. It will send all {@link DataHandler}-Objects that have {@link DataHandlerDescriptor#sendBeforeEnter} set to*
|
||||
* {@code true},
|
||||
*/
|
||||
@Environment(EnvType.CLIENT)
|
||||
public static void sendOnEnter() {
|
||||
getInstance().descriptors.forEach((desc) -> {
|
||||
if (desc.sendBeforeEnter) {
|
||||
BaseDataHandler h = desc.JOIN_INSTANCE.get();
|
||||
if (!h.getOriginatesOnServer()) {
|
||||
getInstance().client.sendToServer(h);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,249 @@
|
|||
package org.betterx.bclib.api.dataexchange.handler.autosync;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.api.dataexchange.DataHandler;
|
||||
import org.betterx.bclib.api.dataexchange.SyncFileHash;
|
||||
import org.betterx.bclib.util.ModUtil;
|
||||
import org.betterx.bclib.util.ModUtil.ModInfo;
|
||||
import org.betterx.bclib.util.Pair;
|
||||
import org.betterx.bclib.util.PathUtil;
|
||||
import org.betterx.bclib.util.Triple;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
class AutoFileSyncEntry extends AutoSyncID {
|
||||
static class ForDirectFileRequest extends AutoFileSyncEntry {
|
||||
final File relFile;
|
||||
|
||||
ForDirectFileRequest(String syncID, File relFile, File absFile) {
|
||||
super(AutoSyncID.ForDirectFileRequest.MOD_ID, syncID, absFile, false, (a, b, c) -> false);
|
||||
this.relFile = relFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int serializeContent(FriendlyByteBuf buf) {
|
||||
int res = super.serializeContent(buf);
|
||||
DataHandler.writeString(buf, relFile.toString());
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static AutoFileSyncEntry.ForDirectFileRequest finishDeserializeContent(String syncID, FriendlyByteBuf buf) {
|
||||
final String relFile = DataHandler.readString(buf);
|
||||
SyncFolderDescriptor desc = AutoSync.getSyncFolderDescriptor(syncID);
|
||||
if (desc != null) {
|
||||
//ensures that the file is not above the base-folder
|
||||
if (desc.acceptChildElements(desc.mapAbsolute(relFile))) {
|
||||
return new AutoFileSyncEntry.ForDirectFileRequest(syncID,
|
||||
new File(relFile),
|
||||
desc.localFolder.resolve(relFile)
|
||||
.normalize()
|
||||
.toFile());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return uniqueID + " - " + relFile;
|
||||
}
|
||||
}
|
||||
|
||||
static class ForModFileRequest extends AutoFileSyncEntry {
|
||||
public static File getLocalPathForID(String modID, boolean matchLocalVersion) {
|
||||
ModInfo mi = ModUtil.getModInfo(modID, matchLocalVersion);
|
||||
if (mi != null) {
|
||||
return mi.jarPath.toFile();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public final String version;
|
||||
|
||||
ForModFileRequest(String modID, boolean matchLocalVersion, String version) {
|
||||
super(modID,
|
||||
AutoSyncID.ForModFileRequest.UNIQUE_ID,
|
||||
getLocalPathForID(modID, matchLocalVersion),
|
||||
false,
|
||||
(a, b, c) -> false);
|
||||
if (this.fileName == null && matchLocalVersion) {
|
||||
BCLib.LOGGER.error("Unknown mod '" + modID + "'.");
|
||||
}
|
||||
if (version == null)
|
||||
this.version = ModUtil.getModVersion(modID);
|
||||
else
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int serializeContent(FriendlyByteBuf buf) {
|
||||
final int res = super.serializeContent(buf);
|
||||
buf.writeInt(ModUtil.convertModVersion(version));
|
||||
return res;
|
||||
}
|
||||
|
||||
static AutoFileSyncEntry.ForModFileRequest finishDeserializeContent(String modID, FriendlyByteBuf buf) {
|
||||
final String version = ModUtil.convertModVersion(buf.readInt());
|
||||
return new AutoFileSyncEntry.ForModFileRequest(modID, false, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Mod " + modID + " (v" + version + ")";
|
||||
}
|
||||
}
|
||||
|
||||
public final AutoSync.NeedTransferPredicate needTransfer;
|
||||
public final File fileName;
|
||||
public final boolean requestContent;
|
||||
private SyncFileHash hash;
|
||||
|
||||
AutoFileSyncEntry(String modID,
|
||||
File fileName,
|
||||
boolean requestContent,
|
||||
AutoSync.NeedTransferPredicate needTransfer) {
|
||||
this(modID, fileName.getName(), fileName, requestContent, needTransfer);
|
||||
}
|
||||
|
||||
AutoFileSyncEntry(String modID,
|
||||
String uniqueID,
|
||||
File fileName,
|
||||
boolean requestContent,
|
||||
AutoSync.NeedTransferPredicate needTransfer) {
|
||||
super(modID, uniqueID);
|
||||
this.needTransfer = needTransfer;
|
||||
this.fileName = fileName;
|
||||
this.requestContent = requestContent;
|
||||
}
|
||||
|
||||
|
||||
public SyncFileHash getFileHash() {
|
||||
if (hash == null) {
|
||||
hash = SyncFileHash.create(modID, fileName, uniqueID);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
public byte[] getContent() {
|
||||
if (!fileName.exists()) return new byte[0];
|
||||
final Path path = fileName.toPath();
|
||||
|
||||
try {
|
||||
return Files.readAllBytes(path);
|
||||
} catch (IOException e) {
|
||||
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
public int serializeContent(FriendlyByteBuf buf) {
|
||||
DataHandler.writeString(buf, modID);
|
||||
DataHandler.writeString(buf, uniqueID);
|
||||
return serializeFileContent(buf);
|
||||
}
|
||||
|
||||
public static Triple<AutoFileSyncEntry, byte[], AutoSyncID> deserializeContent(FriendlyByteBuf buf) {
|
||||
final String modID = DataHandler.readString(buf);
|
||||
final String uniqueID = DataHandler.readString(buf);
|
||||
byte[] data = deserializeFileContent(buf);
|
||||
|
||||
AutoFileSyncEntry entry;
|
||||
if (AutoSyncID.ForDirectFileRequest.MOD_ID.equals(modID)) {
|
||||
entry = AutoFileSyncEntry.ForDirectFileRequest.finishDeserializeContent(uniqueID, buf);
|
||||
} else if (AutoSyncID.ForModFileRequest.UNIQUE_ID.equals(uniqueID)) {
|
||||
entry = AutoFileSyncEntry.ForModFileRequest.finishDeserializeContent(modID, buf);
|
||||
} else {
|
||||
entry = AutoFileSyncEntry.findMatching(modID, uniqueID);
|
||||
}
|
||||
return new Triple<>(entry, data, new AutoSyncID(modID, uniqueID));
|
||||
}
|
||||
|
||||
|
||||
public void serialize(FriendlyByteBuf buf) {
|
||||
getFileHash().serialize(buf);
|
||||
buf.writeBoolean(requestContent);
|
||||
|
||||
if (requestContent) {
|
||||
serializeFileContent(buf);
|
||||
}
|
||||
}
|
||||
|
||||
public static AutoSync.AutoSyncTriple deserializeAndMatch(FriendlyByteBuf buf) {
|
||||
Pair<SyncFileHash, byte[]> e = deserialize(buf);
|
||||
AutoFileSyncEntry match = findMatching(e.first);
|
||||
return new AutoSync.AutoSyncTriple(e.first, e.second, match);
|
||||
}
|
||||
|
||||
public static Pair<SyncFileHash, byte[]> deserialize(FriendlyByteBuf buf) {
|
||||
SyncFileHash hash = SyncFileHash.deserialize(buf);
|
||||
boolean withContent = buf.readBoolean();
|
||||
byte[] data = null;
|
||||
if (withContent) {
|
||||
data = deserializeFileContent(buf);
|
||||
}
|
||||
|
||||
return new Pair(hash, data);
|
||||
}
|
||||
|
||||
private int serializeFileContent(FriendlyByteBuf buf) {
|
||||
if (!PathUtil.isChildOf(PathUtil.GAME_FOLDER, fileName.toPath())) {
|
||||
BCLib.LOGGER.error(fileName + " is not within game folder " + PathUtil.GAME_FOLDER + ". Pretending it does not exist.");
|
||||
buf.writeInt(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
byte[] content = getContent();
|
||||
buf.writeInt(content.length);
|
||||
buf.writeByteArray(content);
|
||||
return content.length;
|
||||
}
|
||||
|
||||
private static byte[] deserializeFileContent(FriendlyByteBuf buf) {
|
||||
byte[] data;
|
||||
int size = buf.readInt();
|
||||
data = buf.readByteArray(size);
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
public static AutoFileSyncEntry findMatching(SyncFileHash hash) {
|
||||
return findMatching(hash.modID, hash.uniqueID);
|
||||
}
|
||||
|
||||
public static AutoFileSyncEntry findMatching(AutoSyncID aid) {
|
||||
if (aid instanceof AutoSyncID.ForDirectFileRequest) {
|
||||
AutoSyncID.ForDirectFileRequest freq = (AutoSyncID.ForDirectFileRequest) aid;
|
||||
SyncFolderDescriptor desc = AutoSync.getSyncFolderDescriptor(freq.uniqueID);
|
||||
if (desc != null) {
|
||||
SyncFolderDescriptor.SubFile subFile = desc.getLocalSubFile(freq.relFile.toString());
|
||||
if (subFile != null) {
|
||||
final File absPath = desc.localFolder.resolve(subFile.relPath)
|
||||
.normalize()
|
||||
.toFile();
|
||||
return new AutoFileSyncEntry.ForDirectFileRequest(freq.uniqueID,
|
||||
new File(subFile.relPath),
|
||||
absPath);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} else if (aid instanceof AutoSyncID.ForModFileRequest) {
|
||||
AutoSyncID.ForModFileRequest mreq = (AutoSyncID.ForModFileRequest) aid;
|
||||
return new AutoFileSyncEntry.ForModFileRequest(mreq.modID, true, null);
|
||||
}
|
||||
return findMatching(aid.modID, aid.uniqueID);
|
||||
}
|
||||
|
||||
public static AutoFileSyncEntry findMatching(String modID, String uniqueID) {
|
||||
return AutoSync.getAutoSyncFiles()
|
||||
.stream()
|
||||
.filter(asf -> asf.modID.equals(modID) && asf.uniqueID.equals(uniqueID))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,199 @@
|
|||
package org.betterx.bclib.api.dataexchange.handler.autosync;
|
||||
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.api.dataexchange.DataExchangeAPI;
|
||||
import org.betterx.bclib.api.dataexchange.SyncFileHash;
|
||||
import org.betterx.bclib.config.Configs;
|
||||
import org.betterx.bclib.config.ServerConfig;
|
||||
import org.betterx.bclib.util.PathUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
public class AutoSync {
|
||||
public static final String SYNC_CATEGORY = "auto_sync";
|
||||
public final static SyncFolderDescriptor SYNC_FOLDER = new SyncFolderDescriptor("BCLIB-SYNC",
|
||||
FabricLoader.getInstance()
|
||||
.getGameDir()
|
||||
.resolve("bclib-sync")
|
||||
.normalize()
|
||||
.toAbsolutePath(),
|
||||
true);
|
||||
|
||||
@FunctionalInterface
|
||||
public interface NeedTransferPredicate {
|
||||
boolean test(SyncFileHash clientHash, SyncFileHash serverHash, FileContentWrapper content);
|
||||
}
|
||||
|
||||
final static class AutoSyncTriple {
|
||||
public final SyncFileHash serverHash;
|
||||
public final byte[] serverContent;
|
||||
public final AutoFileSyncEntry localMatch;
|
||||
|
||||
public AutoSyncTriple(SyncFileHash serverHash, byte[] serverContent, AutoFileSyncEntry localMatch) {
|
||||
this.serverHash = serverHash;
|
||||
this.serverContent = serverContent;
|
||||
this.localMatch = localMatch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return serverHash.modID + "." + serverHash.uniqueID;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ##### File Syncing
|
||||
protected final static List<BiConsumer<AutoSyncID, File>> onWriteCallbacks = new ArrayList<>(2);
|
||||
|
||||
/**
|
||||
* Register a function that is called whenever the client receives a file from the server and replaced toe local
|
||||
* file with the new content.
|
||||
* <p>
|
||||
* This callback is usefull if you need to reload the new content before the game is quit.
|
||||
*
|
||||
* @param callback A Function that receives the AutoSyncID as well as the Filename.
|
||||
*/
|
||||
public static void addOnWriteCallback(BiConsumer<AutoSyncID, File> callback) {
|
||||
onWriteCallbacks.add(callback);
|
||||
}
|
||||
|
||||
private static final List<AutoFileSyncEntry> autoSyncFiles = new ArrayList<>(4);
|
||||
|
||||
public static List<AutoFileSyncEntry> getAutoSyncFiles() {
|
||||
return autoSyncFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a File for automatic client syncing.
|
||||
*
|
||||
* @param modID The ID of the calling Mod
|
||||
* @param needTransfer If the predicate returns true, the file needs to get copied to the server.
|
||||
* @param fileName The name of the File
|
||||
* @param requestContent When {@code true} the content of the file is requested for comparison. This will copy the
|
||||
* entire file from the client to the server.
|
||||
* <p>
|
||||
* You should only use this option, if you need to compare parts of the file in order to decide
|
||||
* If the File needs to be copied. Normally using the {@link SyncFileHash}
|
||||
* for comparison is sufficient.
|
||||
*/
|
||||
public static void addAutoSyncFileData(String modID,
|
||||
File fileName,
|
||||
boolean requestContent,
|
||||
NeedTransferPredicate needTransfer) {
|
||||
if (!PathUtil.isChildOf(PathUtil.GAME_FOLDER, fileName.toPath())) {
|
||||
BCLib.LOGGER.error(fileName + " is outside of Game Folder " + PathUtil.GAME_FOLDER);
|
||||
} else {
|
||||
autoSyncFiles.add(new AutoFileSyncEntry(modID, fileName, requestContent, needTransfer));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a File for automatic client syncing.
|
||||
*
|
||||
* @param modID The ID of the calling Mod
|
||||
* @param uniqueID A unique Identifier for the File. (see {@link SyncFileHash#uniqueID} for
|
||||
* Details
|
||||
* @param needTransfer If the predicate returns true, the file needs to get copied to the server.
|
||||
* @param fileName The name of the File
|
||||
* @param requestContent When {@code true} the content of the file is requested for comparison. This will copy the
|
||||
* entire file from the client to the server.
|
||||
* <p>
|
||||
* You should only use this option, if you need to compare parts of the file in order to decide
|
||||
* If the File needs to be copied. Normally using the {@link SyncFileHash}
|
||||
* for comparison is sufficient.
|
||||
*/
|
||||
public static void addAutoSyncFileData(String modID,
|
||||
String uniqueID,
|
||||
File fileName,
|
||||
boolean requestContent,
|
||||
NeedTransferPredicate needTransfer) {
|
||||
if (!PathUtil.isChildOf(PathUtil.GAME_FOLDER, fileName.toPath())) {
|
||||
BCLib.LOGGER.error(fileName + " is outside of Game Folder " + PathUtil.GAME_FOLDER);
|
||||
} else {
|
||||
autoSyncFiles.add(new AutoFileSyncEntry(modID, uniqueID, fileName, requestContent, needTransfer));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when {@code SendFiles} received a File on the Client and wrote it to the FileSystem.
|
||||
* <p>
|
||||
* This is the place where reload Code should go.
|
||||
*
|
||||
* @param aid The ID of the received File
|
||||
* @param file The location of the FIle on the client
|
||||
*/
|
||||
static void didReceiveFile(AutoSyncID aid, File file) {
|
||||
onWriteCallbacks.forEach(fkt -> fkt.accept(aid, file));
|
||||
}
|
||||
|
||||
|
||||
// ##### Folder Syncing
|
||||
static final List<SyncFolderDescriptor> syncFolderDescriptions = Arrays.asList(SYNC_FOLDER);
|
||||
|
||||
private List<String> syncFolderContent;
|
||||
|
||||
protected List<String> getSyncFolderContent() {
|
||||
if (syncFolderContent == null) {
|
||||
return new ArrayList<>(0);
|
||||
}
|
||||
return syncFolderContent;
|
||||
}
|
||||
|
||||
private static boolean didRegisterAdditionalMods = false;
|
||||
|
||||
//we call this from HelloClient on the Server to prepare transfer
|
||||
protected static void loadSyncFolder() {
|
||||
if (Configs.SERVER_CONFIG.isOfferingFiles()) {
|
||||
syncFolderDescriptions.forEach(desc -> desc.loadCache());
|
||||
}
|
||||
|
||||
if (!didRegisterAdditionalMods && Configs.SERVER_CONFIG.isOfferingMods()) {
|
||||
didRegisterAdditionalMods = true;
|
||||
List<String> modIDs = Configs.SERVER_CONFIG.get(ServerConfig.ADDITIONAL_MODS);
|
||||
if (modIDs != null) {
|
||||
modIDs.stream().forEach(modID -> DataExchangeAPI.registerModDependency(modID));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static SyncFolderDescriptor getSyncFolderDescriptor(String folderID) {
|
||||
return syncFolderDescriptions.stream()
|
||||
.filter(d -> d.equals(folderID))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
protected static Path localBasePathForFolderID(String folderID) {
|
||||
final SyncFolderDescriptor desc = getSyncFolderDescriptor(folderID);
|
||||
if (desc != null) {
|
||||
return desc.localFolder;
|
||||
} else {
|
||||
BCLib.LOGGER.warning("Unknown Sync-Folder ID '" + folderID + "'");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void registerSyncFolder(String folderID, Path localBaseFolder, boolean removeAdditionalFiles) {
|
||||
localBaseFolder = localBaseFolder.normalize();
|
||||
if (PathUtil.isChildOf(PathUtil.GAME_FOLDER, localBaseFolder)) {
|
||||
final SyncFolderDescriptor desc = new SyncFolderDescriptor(folderID,
|
||||
localBaseFolder,
|
||||
removeAdditionalFiles);
|
||||
if (syncFolderDescriptions.contains(desc)) {
|
||||
BCLib.LOGGER.warning("Tried to override Folder Sync '" + folderID + "' again.");
|
||||
} else {
|
||||
syncFolderDescriptions.add(desc);
|
||||
}
|
||||
} else {
|
||||
BCLib.LOGGER.error(localBaseFolder + " (from " + folderID + ") is outside the game directory " + PathUtil.GAME_FOLDER + ". Sync is not allowed.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
package org.betterx.bclib.api.dataexchange.handler.autosync;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
|
||||
import org.betterx.bclib.api.dataexchange.DataHandler;
|
||||
import org.betterx.bclib.config.Config;
|
||||
import org.betterx.bclib.util.ModUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Objects;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class AutoSyncID {
|
||||
static class WithContentOverride extends AutoSyncID {
|
||||
final FileContentWrapper contentWrapper;
|
||||
final File localFile;
|
||||
|
||||
WithContentOverride(String modID, String uniqueID, FileContentWrapper contentWrapper, File localFile) {
|
||||
super(modID, uniqueID);
|
||||
this.contentWrapper = contentWrapper;
|
||||
this.localFile = localFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + " (Content override)";
|
||||
}
|
||||
}
|
||||
|
||||
static class ForDirectFileRequest extends AutoSyncID {
|
||||
public final static String MOD_ID = "bclib::FILE";
|
||||
final File relFile;
|
||||
|
||||
ForDirectFileRequest(String syncID, File relFile) {
|
||||
super(ForDirectFileRequest.MOD_ID, syncID);
|
||||
this.relFile = relFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
void serializeData(FriendlyByteBuf buf) {
|
||||
super.serializeData(buf);
|
||||
DataHandler.writeString(buf, relFile.toString());
|
||||
}
|
||||
|
||||
static ForDirectFileRequest finishDeserialize(String modID, String uniqueID, FriendlyByteBuf buf) {
|
||||
final File fl = new File(DataHandler.readString(buf));
|
||||
return new ForDirectFileRequest(uniqueID, fl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.uniqueID + " (" + this.relFile + ")";
|
||||
}
|
||||
}
|
||||
|
||||
static class ForModFileRequest extends AutoSyncID {
|
||||
public final static String UNIQUE_ID = "bclib::MOD";
|
||||
private final String version;
|
||||
|
||||
ForModFileRequest(String modID, String version) {
|
||||
super(modID, ForModFileRequest.UNIQUE_ID);
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
void serializeData(FriendlyByteBuf buf) {
|
||||
super.serializeData(buf);
|
||||
buf.writeInt(ModUtil.convertModVersion(version));
|
||||
}
|
||||
|
||||
static ForModFileRequest finishDeserialize(String modID, String uniqueID, FriendlyByteBuf buf) {
|
||||
final String version = ModUtil.convertModVersion(buf.readInt());
|
||||
return new ForModFileRequest(modID, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.modID + " (v" + this.version + ")";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Unique ID for the referenced File.
|
||||
* <p>
|
||||
* Files with the same {@link #modID} need to have a unique IDs. Normally the filename from FileHash(String, File, byte[], int, int)
|
||||
* is used to generated that ID, but you can directly specify one using FileHash(String, String, byte[], int, int).
|
||||
*/
|
||||
@NotNull
|
||||
public final String uniqueID;
|
||||
|
||||
/**
|
||||
* The ID of the Mod that is registering the File
|
||||
*/
|
||||
@NotNull
|
||||
public final String modID;
|
||||
|
||||
public AutoSyncID(String modID, String uniqueID) {
|
||||
Objects.nonNull(modID);
|
||||
Objects.nonNull(uniqueID);
|
||||
|
||||
this.modID = modID;
|
||||
this.uniqueID = uniqueID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return modID + "." + uniqueID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof AutoSyncID)) return false;
|
||||
AutoSyncID that = (AutoSyncID) o;
|
||||
return uniqueID.equals(that.uniqueID) && modID.equals(that.modID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(uniqueID, modID);
|
||||
}
|
||||
|
||||
void serializeData(FriendlyByteBuf buf) {
|
||||
DataHandler.writeString(buf, modID);
|
||||
DataHandler.writeString(buf, uniqueID);
|
||||
}
|
||||
|
||||
static AutoSyncID deserializeData(FriendlyByteBuf buf) {
|
||||
String modID = DataHandler.readString(buf);
|
||||
String uID = DataHandler.readString(buf);
|
||||
|
||||
if (ForDirectFileRequest.MOD_ID.equals(modID)) {
|
||||
return ForDirectFileRequest.finishDeserialize(modID, uID, buf);
|
||||
} else if (ForModFileRequest.UNIQUE_ID.equals(uID)) {
|
||||
return ForModFileRequest.finishDeserialize(modID, uID, buf);
|
||||
} else {
|
||||
return new AutoSyncID(modID, uID);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isConfigFile() {
|
||||
return this.uniqueID.startsWith(Config.CONFIG_SYNC_PREFIX);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,276 @@
|
|||
package org.betterx.bclib.api.dataexchange.handler.autosync;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.util.ProgressListener;
|
||||
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.api.dataexchange.BaseDataHandler;
|
||||
import org.betterx.bclib.api.dataexchange.DataHandler;
|
||||
import org.betterx.bclib.api.dataexchange.DataHandlerDescriptor;
|
||||
import org.betterx.bclib.api.dataexchange.handler.DataExchange;
|
||||
|
||||
import java.util.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Used to seperate large data transfers into multiple smaller messages.
|
||||
* <p>
|
||||
* {@link DataHandler} will automatically convert larger messages into Chunks on the Server
|
||||
* and assemble the original message from those chunks on the client.
|
||||
*/
|
||||
public class Chunker extends DataHandler.FromServer {
|
||||
|
||||
/**
|
||||
* Responsible for assembling the original ByteBuffer created by {@link PacketChunkSender} on the
|
||||
* receiving end. Automatically created from the header {@link Chunker}-Message (where the serialNo==-1)
|
||||
*/
|
||||
static class PacketChunkReceiver {
|
||||
@NotNull
|
||||
public final UUID uuid;
|
||||
public final int chunkCount;
|
||||
@NotNull
|
||||
private final FriendlyByteBuf networkedBuf;
|
||||
@Nullable
|
||||
private final DataHandlerDescriptor descriptor;
|
||||
|
||||
private static final List<PacketChunkReceiver> active = new ArrayList<>(1);
|
||||
|
||||
private static PacketChunkReceiver newReceiver(@NotNull UUID uuid, int chunkCount, ResourceLocation origin) {
|
||||
DataHandlerDescriptor desc = DataExchange.getDescriptor(origin);
|
||||
final PacketChunkReceiver r = new PacketChunkReceiver(uuid, chunkCount, desc);
|
||||
active.add(r);
|
||||
return r;
|
||||
}
|
||||
|
||||
private static PacketChunkReceiver getOrCreate(@NotNull UUID uuid, int chunkCount, ResourceLocation origin) {
|
||||
return active.stream()
|
||||
.filter(r -> r.uuid.equals(uuid))
|
||||
.findFirst()
|
||||
.orElse(newReceiver(uuid, chunkCount, origin));
|
||||
}
|
||||
|
||||
public static PacketChunkReceiver get(@NotNull UUID uuid) {
|
||||
return active.stream().filter(r -> r.uuid.equals(uuid)).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
private PacketChunkReceiver(@NotNull UUID uuid, int chunkCount, @Nullable DataHandlerDescriptor descriptor) {
|
||||
this.uuid = uuid;
|
||||
this.chunkCount = chunkCount;
|
||||
networkedBuf = PacketByteBufs.create();
|
||||
this.descriptor = descriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof PacketChunkReceiver)) return false;
|
||||
PacketChunkReceiver that = (PacketChunkReceiver) o;
|
||||
return uuid.equals(that.uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(uuid);
|
||||
}
|
||||
|
||||
public boolean testFinished() {
|
||||
ProgressListener listener = ChunkerProgress.getProgressListener();
|
||||
if (listener != null) {
|
||||
listener.progressStagePercentage((100 * receivedCount) / chunkCount);
|
||||
}
|
||||
if (incomingBuffer == null) {
|
||||
return true;
|
||||
}
|
||||
if (lastReadSerial >= chunkCount - 1) {
|
||||
onFinish();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void addBuffer(FriendlyByteBuf input) {
|
||||
final int size = input.readableBytes();
|
||||
final int cap = networkedBuf.capacity() - networkedBuf.writerIndex();
|
||||
|
||||
if (cap < size) {
|
||||
networkedBuf.capacity(networkedBuf.writerIndex() + size);
|
||||
}
|
||||
input.readBytes(networkedBuf, size);
|
||||
input.clear();
|
||||
}
|
||||
|
||||
protected void onFinish() {
|
||||
incomingBuffer.clear();
|
||||
incomingBuffer = null;
|
||||
|
||||
final BaseDataHandler baseHandler = descriptor.INSTANCE.get();
|
||||
if (baseHandler instanceof DataHandler.FromServer handler) {
|
||||
handler.receiveFromMemory(networkedBuf);
|
||||
}
|
||||
}
|
||||
|
||||
Map<Integer, FriendlyByteBuf> incomingBuffer = new HashMap<>();
|
||||
int lastReadSerial = -1;
|
||||
int receivedCount = 0;
|
||||
|
||||
public void processReceived(FriendlyByteBuf buf, int serialNo, int size) {
|
||||
receivedCount++;
|
||||
|
||||
if (lastReadSerial == serialNo - 1) {
|
||||
addBuffer(buf);
|
||||
lastReadSerial = serialNo;
|
||||
} else {
|
||||
//not sure if order is guaranteed by the underlying system!
|
||||
boolean haveAll = true;
|
||||
for (int nr = lastReadSerial + 1; nr < serialNo - 1; nr++) {
|
||||
if (incomingBuffer.get(nr) == null) {
|
||||
haveAll = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (haveAll) {
|
||||
for (int nr = lastReadSerial + 1; nr < serialNo - 1; nr++) {
|
||||
addBuffer(incomingBuffer.get(nr));
|
||||
incomingBuffer.put(nr, null);
|
||||
}
|
||||
addBuffer(buf);
|
||||
lastReadSerial = serialNo;
|
||||
} else {
|
||||
incomingBuffer.put(serialNo, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Responsible for splitting an outgoing ByteBuffer into several smaller Chunks and
|
||||
* send them as seperate messages to the {@link Chunker}-Channel
|
||||
*/
|
||||
public static class PacketChunkSender {
|
||||
private final FriendlyByteBuf networkedBuf;
|
||||
public final UUID uuid;
|
||||
public final int chunkCount;
|
||||
public final int size;
|
||||
public final ResourceLocation origin;
|
||||
|
||||
public PacketChunkSender(FriendlyByteBuf buf, ResourceLocation origin) {
|
||||
networkedBuf = buf;
|
||||
|
||||
size = buf.readableBytes();
|
||||
chunkCount = (int) Math.ceil((double) size / MAX_PAYLOAD_SIZE);
|
||||
uuid = UUID.randomUUID();
|
||||
this.origin = origin;
|
||||
}
|
||||
|
||||
public void sendChunks(Collection<ServerPlayer> players) {
|
||||
BCLib.LOGGER.info("Sending Request in " + chunkCount + " Packet-Chunks");
|
||||
for (int i = -1; i < chunkCount; i++) {
|
||||
Chunker c = new Chunker(i, uuid, networkedBuf, chunkCount, origin);
|
||||
FriendlyByteBuf buf = PacketByteBufs.create();
|
||||
c.serializeDataOnServer(buf);
|
||||
|
||||
for (ServerPlayer player : players) {
|
||||
ServerPlayNetworking.send(player, DESCRIPTOR.IDENTIFIER, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//header = version + UUID + serialNo + size, see serializeDataOnServer
|
||||
private static final int HEADER_SIZE = 1 + 16 + 4 + 4;
|
||||
|
||||
public static final int MAX_PACKET_SIZE = 1024 * 1024;
|
||||
private static final int MAX_PAYLOAD_SIZE = MAX_PACKET_SIZE - HEADER_SIZE;
|
||||
|
||||
public static final DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor(new ResourceLocation(BCLib.MOD_ID,
|
||||
"chunker"),
|
||||
Chunker::new,
|
||||
false,
|
||||
false);
|
||||
|
||||
private int serialNo;
|
||||
private UUID uuid;
|
||||
private int chunkCount;
|
||||
private FriendlyByteBuf networkedBuf;
|
||||
private ResourceLocation origin;
|
||||
|
||||
protected Chunker(int serialNo, UUID uuid, FriendlyByteBuf networkedBuf, int chunkCount, ResourceLocation origin) {
|
||||
super(DESCRIPTOR.IDENTIFIER);
|
||||
this.serialNo = serialNo;
|
||||
this.uuid = uuid;
|
||||
this.networkedBuf = networkedBuf;
|
||||
this.chunkCount = chunkCount;
|
||||
this.origin = origin;
|
||||
}
|
||||
|
||||
protected Chunker() {
|
||||
super(DESCRIPTOR.IDENTIFIER);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void serializeDataOnServer(FriendlyByteBuf buf) {
|
||||
//Sending Header. Make sure to change HEADER_SIZE if you change this!
|
||||
buf.writeByte(0);
|
||||
buf.writeLong(uuid.getMostSignificantBits());
|
||||
buf.writeLong(uuid.getLeastSignificantBits());
|
||||
buf.writeInt(serialNo);
|
||||
|
||||
//sending Payload
|
||||
if (serialNo == -1) {
|
||||
//this is our header-Chunk that transports status information
|
||||
buf.writeInt(chunkCount);
|
||||
writeString(buf, origin.getNamespace());
|
||||
writeString(buf, origin.getPath());
|
||||
} else {
|
||||
//this is an actual payload chunk
|
||||
buf.capacity(MAX_PACKET_SIZE);
|
||||
final int size = Math.min(MAX_PAYLOAD_SIZE, networkedBuf.readableBytes());
|
||||
buf.writeInt(size);
|
||||
networkedBuf.readBytes(buf, size);
|
||||
}
|
||||
}
|
||||
|
||||
private PacketChunkReceiver receiver;
|
||||
|
||||
@Override
|
||||
protected void deserializeIncomingDataOnClient(FriendlyByteBuf buf, PacketSender responseSender) {
|
||||
final int version = buf.readByte();
|
||||
uuid = new UUID(buf.readLong(), buf.readLong());
|
||||
serialNo = buf.readInt();
|
||||
|
||||
if (serialNo == -1) {
|
||||
chunkCount = buf.readInt();
|
||||
final String namespace = readString(buf);
|
||||
final String path = readString(buf);
|
||||
ResourceLocation ident = new ResourceLocation(namespace, path);
|
||||
BCLib.LOGGER.info("Receiving " + chunkCount + " + Packet-Chunks for " + ident);
|
||||
|
||||
receiver = PacketChunkReceiver.getOrCreate(uuid, chunkCount, ident);
|
||||
} else {
|
||||
receiver = PacketChunkReceiver.get(uuid);
|
||||
if (receiver != null) {
|
||||
final int size = buf.readInt();
|
||||
receiver.processReceived(buf, serialNo, size);
|
||||
} else {
|
||||
BCLib.LOGGER.error("Unknown Packet-Chunk Transfer for " + uuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void runOnClientGameThread(Minecraft client) {
|
||||
if (receiver != null) {
|
||||
receiver.testFinished();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package org.betterx.bclib.api.dataexchange.handler.autosync;
|
||||
|
||||
import net.minecraft.util.ProgressListener;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
|
||||
import org.betterx.bclib.gui.screens.ProgressScreen;
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
public class ChunkerProgress {
|
||||
private static ProgressScreen progressScreen;
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
public static void setProgressScreen(ProgressScreen scr) {
|
||||
progressScreen = scr;
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
public static ProgressScreen getProgressScreen() {
|
||||
return progressScreen;
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
public static ProgressListener getProgressListener() {
|
||||
return progressScreen;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
package org.betterx.bclib.api.dataexchange.handler.autosync;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class FileContentWrapper {
|
||||
private byte[] rawContent;
|
||||
private ByteArrayOutputStream outputStream;
|
||||
|
||||
FileContentWrapper(byte[] content) {
|
||||
this.rawContent = content;
|
||||
this.outputStream = null;
|
||||
}
|
||||
|
||||
public byte[] getOriginalContent() {
|
||||
return rawContent;
|
||||
}
|
||||
|
||||
public byte[] getRawContent() {
|
||||
if (outputStream != null) {
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
return rawContent;
|
||||
}
|
||||
|
||||
private void invalidateOutputStream() {
|
||||
if (this.outputStream != null) {
|
||||
try {
|
||||
this.outputStream.close();
|
||||
} catch (IOException e) {
|
||||
BCLib.LOGGER.debug(e);
|
||||
}
|
||||
}
|
||||
this.outputStream = null;
|
||||
}
|
||||
|
||||
public void setRawContent(byte[] rawContent) {
|
||||
this.rawContent = rawContent;
|
||||
invalidateOutputStream();
|
||||
}
|
||||
|
||||
public void syncWithOutputStream() {
|
||||
if (outputStream != null) {
|
||||
try {
|
||||
outputStream.flush();
|
||||
} catch (IOException e) {
|
||||
BCLib.LOGGER.error(e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
setRawContent(getRawContent());
|
||||
invalidateOutputStream();
|
||||
}
|
||||
}
|
||||
|
||||
public ByteArrayInputStream getInputStream() {
|
||||
if (rawContent == null) return new ByteArrayInputStream(new byte[0]);
|
||||
return new ByteArrayInputStream(rawContent);
|
||||
}
|
||||
|
||||
public ByteArrayOutputStream getOrCreateOutputStream() {
|
||||
if (this.outputStream == null) {
|
||||
return this.getEmptyOutputStream();
|
||||
}
|
||||
return this.outputStream;
|
||||
}
|
||||
|
||||
public ByteArrayOutputStream getEmptyOutputStream() {
|
||||
invalidateOutputStream();
|
||||
this.outputStream = new ByteArrayOutputStream(this.rawContent.length);
|
||||
return this.outputStream;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,509 @@
|
|||
package org.betterx.bclib.api.dataexchange.handler.autosync;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.chat.CommonComponents;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||
import net.fabricmc.loader.api.metadata.ModEnvironment;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.api.dataexchange.DataExchangeAPI;
|
||||
import org.betterx.bclib.api.dataexchange.DataHandler;
|
||||
import org.betterx.bclib.api.dataexchange.DataHandlerDescriptor;
|
||||
import org.betterx.bclib.config.Configs;
|
||||
import org.betterx.bclib.config.ServerConfig;
|
||||
import org.betterx.bclib.gui.screens.ModListScreen;
|
||||
import org.betterx.bclib.gui.screens.ProgressScreen;
|
||||
import org.betterx.bclib.gui.screens.SyncFilesScreen;
|
||||
import org.betterx.bclib.gui.screens.WarnBCLibVersionMismatch;
|
||||
import org.betterx.bclib.util.ModUtil;
|
||||
import org.betterx.bclib.util.ModUtil.ModInfo;
|
||||
import org.betterx.bclib.util.PathUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Sent from the Server to the Client.
|
||||
* <p>
|
||||
* For Details refer to {@link HelloServer}
|
||||
*/
|
||||
public class HelloClient extends DataHandler.FromServer {
|
||||
public record OfferedModInfo(String version, int size, boolean canDownload) {
|
||||
}
|
||||
|
||||
public interface IServerModMap extends Map<String, OfferedModInfo> {
|
||||
}
|
||||
|
||||
public static class ServerModMap extends HashMap<String, OfferedModInfo> implements IServerModMap {
|
||||
}
|
||||
|
||||
public static final DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor(new ResourceLocation(BCLib.MOD_ID,
|
||||
"hello_client"),
|
||||
HelloClient::new,
|
||||
false,
|
||||
false);
|
||||
|
||||
public HelloClient() {
|
||||
super(DESCRIPTOR.IDENTIFIER);
|
||||
}
|
||||
|
||||
static String getBCLibVersion() {
|
||||
return ModUtil.getModVersion(BCLib.MOD_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean prepareDataOnServer() {
|
||||
if (!Configs.SERVER_CONFIG.isAllowingAutoSync()) {
|
||||
BCLib.LOGGER.info("Auto-Sync was disabled on the server.");
|
||||
return false;
|
||||
}
|
||||
|
||||
AutoSync.loadSyncFolder();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void serializeDataOnServer(FriendlyByteBuf buf) {
|
||||
final String vbclib = getBCLibVersion();
|
||||
BCLib.LOGGER.info("Sending Hello to Client. (server=" + vbclib + ")");
|
||||
|
||||
//write BCLibVersion (=protocol version)
|
||||
buf.writeInt(ModUtil.convertModVersion(vbclib));
|
||||
|
||||
if (Configs.SERVER_CONFIG.isOfferingMods() || Configs.SERVER_CONFIG.isOfferingInfosForMods()) {
|
||||
List<String> mods = DataExchangeAPI.registeredMods();
|
||||
final List<String> inmods = mods;
|
||||
if (Configs.SERVER_CONFIG.isOfferingAllMods() || Configs.SERVER_CONFIG.isOfferingInfosForMods()) {
|
||||
mods = new ArrayList<>(inmods.size());
|
||||
mods.addAll(inmods);
|
||||
mods.addAll(ModUtil
|
||||
.getMods()
|
||||
.entrySet()
|
||||
.stream()
|
||||
.filter(entry -> entry.getValue().metadata.getEnvironment() != ModEnvironment.SERVER && !inmods.contains(
|
||||
entry.getKey()))
|
||||
.map(entry -> entry.getKey())
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
|
||||
mods = mods
|
||||
.stream()
|
||||
.filter(entry -> !Configs.SERVER_CONFIG.get(ServerConfig.EXCLUDED_MODS).contains(entry))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
//write Plugin Versions
|
||||
buf.writeInt(mods.size());
|
||||
for (String modID : mods) {
|
||||
final String ver = ModUtil.getModVersion(modID);
|
||||
int size = 0;
|
||||
|
||||
final ModInfo mi = ModUtil.getModInfo(modID);
|
||||
if (mi != null) {
|
||||
try {
|
||||
size = (int) Files.size(mi.jarPath);
|
||||
} catch (IOException e) {
|
||||
BCLib.LOGGER.error("Unable to get File Size: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
writeString(buf, modID);
|
||||
buf.writeInt(ModUtil.convertModVersion(ver));
|
||||
buf.writeInt(size);
|
||||
final boolean canDownload = size > 0 && Configs.SERVER_CONFIG.isOfferingMods() && (Configs.SERVER_CONFIG.isOfferingAllMods() || inmods.contains(
|
||||
modID));
|
||||
buf.writeBoolean(canDownload);
|
||||
|
||||
BCLib.LOGGER.info(" - Listing Mod " + modID + " v" + ver + " (size: " + PathUtil.humanReadableFileSize(
|
||||
size) + ", download=" + canDownload + ")");
|
||||
}
|
||||
} else {
|
||||
BCLib.LOGGER.info("Server will not list Mods.");
|
||||
buf.writeInt(0);
|
||||
}
|
||||
|
||||
if (Configs.SERVER_CONFIG.isOfferingFiles() || Configs.SERVER_CONFIG.isOfferingConfigs()) {
|
||||
//do only include files that exist on the server
|
||||
final List<AutoFileSyncEntry> existingAutoSyncFiles = AutoSync.getAutoSyncFiles()
|
||||
.stream()
|
||||
.filter(e -> e.fileName.exists())
|
||||
.filter(e -> (e.isConfigFile() && Configs.SERVER_CONFIG.isOfferingConfigs()) || (e instanceof AutoFileSyncEntry.ForDirectFileRequest && Configs.SERVER_CONFIG.isOfferingFiles()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
//send config Data
|
||||
buf.writeInt(existingAutoSyncFiles.size());
|
||||
for (AutoFileSyncEntry entry : existingAutoSyncFiles) {
|
||||
entry.serialize(buf);
|
||||
BCLib.LOGGER.info(" - Offering " + (entry.isConfigFile() ? "Config " : "File ") + entry);
|
||||
}
|
||||
} else {
|
||||
BCLib.LOGGER.info("Server will neither offer Files nor Configs.");
|
||||
buf.writeInt(0);
|
||||
}
|
||||
|
||||
if (Configs.SERVER_CONFIG.isOfferingFiles()) {
|
||||
buf.writeInt(AutoSync.syncFolderDescriptions.size());
|
||||
AutoSync.syncFolderDescriptions.forEach(desc -> {
|
||||
BCLib.LOGGER.info(" - Offering Folder " + desc.localFolder + " (allowDelete=" + desc.removeAdditionalFiles + ")");
|
||||
desc.serialize(buf);
|
||||
});
|
||||
} else {
|
||||
BCLib.LOGGER.info("Server will not offer Sync Folders.");
|
||||
buf.writeInt(0);
|
||||
}
|
||||
|
||||
buf.writeBoolean(Configs.SERVER_CONFIG.isOfferingInfosForMods());
|
||||
}
|
||||
|
||||
String bclibVersion = "0.0.0";
|
||||
|
||||
|
||||
IServerModMap modVersion = new ServerModMap();
|
||||
List<AutoSync.AutoSyncTriple> autoSyncedFiles = null;
|
||||
List<SyncFolderDescriptor> autoSynFolders = null;
|
||||
boolean serverPublishedModInfo = false;
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
@Override
|
||||
protected void deserializeIncomingDataOnClient(FriendlyByteBuf buf, PacketSender responseSender) {
|
||||
//read BCLibVersion (=protocol version)
|
||||
bclibVersion = ModUtil.convertModVersion(buf.readInt());
|
||||
|
||||
//read Plugin Versions
|
||||
modVersion = new ServerModMap();
|
||||
int count = buf.readInt();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final String id = readString(buf);
|
||||
final String version = ModUtil.convertModVersion(buf.readInt());
|
||||
final int size;
|
||||
final boolean canDownload;
|
||||
//since v0.4.1 we also send the size of the mod-File
|
||||
size = buf.readInt();
|
||||
canDownload = buf.readBoolean();
|
||||
modVersion.put(id, new OfferedModInfo(version, size, canDownload));
|
||||
}
|
||||
|
||||
//read config Data
|
||||
count = buf.readInt();
|
||||
autoSyncedFiles = new ArrayList<>(count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
//System.out.println("Deserializing ");
|
||||
AutoSync.AutoSyncTriple t = AutoFileSyncEntry.deserializeAndMatch(buf);
|
||||
autoSyncedFiles.add(t);
|
||||
//System.out.println(t.first);
|
||||
}
|
||||
|
||||
|
||||
autoSynFolders = new ArrayList<>(1);
|
||||
//since v0.4.1 we also send the sync folders
|
||||
final int folderCount = buf.readInt();
|
||||
for (int i = 0; i < folderCount; i++) {
|
||||
SyncFolderDescriptor desc = SyncFolderDescriptor.deserialize(buf);
|
||||
autoSynFolders.add(desc);
|
||||
}
|
||||
|
||||
serverPublishedModInfo = buf.readBoolean();
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
private void processAutoSyncFolder(final List<AutoSyncID> filesToRequest,
|
||||
final List<AutoSyncID.ForDirectFileRequest> filesToRemove) {
|
||||
if (!Configs.CLIENT_CONFIG.isAcceptingFiles()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (autoSynFolders.size() > 0) {
|
||||
BCLib.LOGGER.info("Folders offered by Server:");
|
||||
}
|
||||
|
||||
autoSynFolders.forEach(desc -> {
|
||||
//desc contains the fileCache sent from the server, load the local version to get hold of the actual file cache on the client
|
||||
SyncFolderDescriptor localDescriptor = AutoSync.getSyncFolderDescriptor(desc.folderID);
|
||||
if (localDescriptor != null) {
|
||||
BCLib.LOGGER.info(" - " + desc.folderID + " (" + desc.localFolder + ", allowRemove=" + desc.removeAdditionalFiles + ")");
|
||||
localDescriptor.invalidateCache();
|
||||
|
||||
desc.relativeFilesStream()
|
||||
.filter(desc::discardChildElements)
|
||||
.forEach(subFile -> {
|
||||
BCLib.LOGGER.warning(" * " + subFile.relPath + " (REJECTED)");
|
||||
});
|
||||
|
||||
|
||||
if (desc.removeAdditionalFiles) {
|
||||
List<AutoSyncID.ForDirectFileRequest> additionalFiles = localDescriptor.relativeFilesStream()
|
||||
.filter(subFile -> !desc.hasRelativeFile(
|
||||
subFile))
|
||||
.map(desc::mapAbsolute)
|
||||
.filter(desc::acceptChildElements)
|
||||
.map(absPath -> new AutoSyncID.ForDirectFileRequest(
|
||||
desc.folderID,
|
||||
absPath.toFile()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
additionalFiles.forEach(aid -> BCLib.LOGGER.info(" * " + desc.localFolder.relativize(aid.relFile.toPath()) + " (missing on server)"));
|
||||
filesToRemove.addAll(additionalFiles);
|
||||
}
|
||||
|
||||
desc.relativeFilesStream()
|
||||
.filter(desc::acceptChildElements)
|
||||
.forEach(subFile -> {
|
||||
SyncFolderDescriptor.SubFile localSubFile = localDescriptor.getLocalSubFile(subFile.relPath);
|
||||
if (localSubFile != null) {
|
||||
//the file exists locally, check if the hashes match
|
||||
if (!localSubFile.hash.equals(subFile.hash)) {
|
||||
BCLib.LOGGER.info(" * " + subFile.relPath + " (changed)");
|
||||
filesToRequest.add(new AutoSyncID.ForDirectFileRequest(desc.folderID,
|
||||
new File(subFile.relPath)));
|
||||
} else {
|
||||
BCLib.LOGGER.info(" * " + subFile.relPath);
|
||||
}
|
||||
} else {
|
||||
//the file is missing locally
|
||||
BCLib.LOGGER.info(" * " + subFile.relPath + " (missing on client)");
|
||||
filesToRequest.add(new AutoSyncID.ForDirectFileRequest(desc.folderID,
|
||||
new File(subFile.relPath)));
|
||||
}
|
||||
});
|
||||
|
||||
//free some memory
|
||||
localDescriptor.invalidateCache();
|
||||
} else {
|
||||
BCLib.LOGGER.info(" - " + desc.folderID + " (Failed to find)");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
private void processSingleFileSync(final List<AutoSyncID> filesToRequest) {
|
||||
final boolean debugHashes = Configs.CLIENT_CONFIG.shouldPrintDebugHashes();
|
||||
|
||||
if (autoSyncedFiles.size() > 0) {
|
||||
BCLib.LOGGER.info("Files offered by Server:");
|
||||
}
|
||||
|
||||
//Handle single sync files
|
||||
//Single files need to be registered for sync on both client and server
|
||||
//There are no restrictions to the target folder, but the client decides the final
|
||||
//location.
|
||||
for (AutoSync.AutoSyncTriple e : autoSyncedFiles) {
|
||||
String actionString = "";
|
||||
FileContentWrapper contentWrapper = new FileContentWrapper(e.serverContent);
|
||||
if (e.localMatch == null) {
|
||||
actionString = "(unknown source -> omitting)";
|
||||
//filesToRequest.add(new AutoSyncID(e.serverHash.modID, e.serverHash.uniqueID));
|
||||
} else if (e.localMatch.needTransfer.test(e.localMatch.getFileHash(), e.serverHash, contentWrapper)) {
|
||||
actionString = "(prepare update)";
|
||||
//we did not yet receive the new content
|
||||
if (contentWrapper.getRawContent() == null) {
|
||||
filesToRequest.add(new AutoSyncID(e.serverHash.modID, e.serverHash.uniqueID));
|
||||
} else {
|
||||
filesToRequest.add(new AutoSyncID.WithContentOverride(e.serverHash.modID,
|
||||
e.serverHash.uniqueID,
|
||||
contentWrapper,
|
||||
e.localMatch.fileName));
|
||||
}
|
||||
}
|
||||
|
||||
BCLib.LOGGER.info(" - " + e + ": " + actionString);
|
||||
if (debugHashes) {
|
||||
BCLib.LOGGER.info(" * " + e.serverHash + " (Server)");
|
||||
BCLib.LOGGER.info(" * " + e.localMatch.getFileHash() + " (Client)");
|
||||
BCLib.LOGGER.info(" * local Content " + (contentWrapper.getRawContent() == null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
private void processModFileSync(final List<AutoSyncID> filesToRequest, final Set<String> mismatchingMods) {
|
||||
for (Entry<String, OfferedModInfo> e : modVersion.entrySet()) {
|
||||
final String localVersion = ModUtil.convertModVersion(ModUtil.convertModVersion(ModUtil.getModVersion(e.getKey())));
|
||||
final OfferedModInfo serverInfo = e.getValue();
|
||||
|
||||
ModInfo nfo = ModUtil.getModInfo(e.getKey());
|
||||
final boolean clientOnly = nfo != null && nfo.metadata.getEnvironment() == ModEnvironment.CLIENT;
|
||||
final boolean requestMod = !clientOnly && !serverInfo.version.equals(localVersion) && serverInfo.size > 0 && serverInfo.canDownload;
|
||||
|
||||
BCLib.LOGGER.info(" - " + e.getKey() + " (client=" + localVersion + ", server=" + serverInfo.version + ", size=" + PathUtil.humanReadableFileSize(
|
||||
serverInfo.size) + (requestMod ? ", requesting" : "") + (serverInfo.canDownload
|
||||
? ""
|
||||
: ", not offered") + (clientOnly ? ", client only" : "") + ")");
|
||||
if (requestMod) {
|
||||
filesToRequest.add(new AutoSyncID.ForModFileRequest(e.getKey(), serverInfo.version));
|
||||
}
|
||||
if (!serverInfo.version.equals(localVersion)) {
|
||||
mismatchingMods.add(e.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
mismatchingMods.addAll(ModListScreen.localMissing(modVersion));
|
||||
mismatchingMods.addAll(ModListScreen.serverMissing(modVersion));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isBlocking() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
@Override
|
||||
protected void runOnClientGameThread(Minecraft client) {
|
||||
if (!Configs.CLIENT_CONFIG.isAllowingAutoSync()) {
|
||||
BCLib.LOGGER.info("Auto-Sync was disabled on the client.");
|
||||
return;
|
||||
}
|
||||
final String localBclibVersion = getBCLibVersion();
|
||||
BCLib.LOGGER.info("Received Hello from Server. (client=" + localBclibVersion + ", server=" + bclibVersion + ")");
|
||||
|
||||
if (ModUtil.convertModVersion(localBclibVersion) != ModUtil.convertModVersion(bclibVersion)) {
|
||||
showBCLibError(client);
|
||||
return;
|
||||
}
|
||||
|
||||
final List<AutoSyncID> filesToRequest = new ArrayList<>(2);
|
||||
final List<AutoSyncID.ForDirectFileRequest> filesToRemove = new ArrayList<>(2);
|
||||
final Set<String> mismatchingMods = new HashSet<>(2);
|
||||
|
||||
|
||||
processModFileSync(filesToRequest, mismatchingMods);
|
||||
processSingleFileSync(filesToRequest);
|
||||
processAutoSyncFolder(filesToRequest, filesToRemove);
|
||||
|
||||
//Handle folder sync
|
||||
//Both client and server need to know about the folder you want to sync
|
||||
//Files can only get placed within that folder
|
||||
|
||||
if ((filesToRequest.size() > 0 || filesToRemove.size() > 0) && (Configs.CLIENT_CONFIG.isAcceptingMods() || Configs.CLIENT_CONFIG.isAcceptingConfigs() || Configs.CLIENT_CONFIG.isAcceptingFiles())) {
|
||||
showSyncFilesScreen(client, filesToRequest, filesToRemove);
|
||||
return;
|
||||
} else if (serverPublishedModInfo && mismatchingMods.size() > 0 && Configs.CLIENT_CONFIG.isShowingModInfo()) {
|
||||
client.setScreen(new ModListScreen(client.screen,
|
||||
Component.translatable("title.bclib.modmissmatch"),
|
||||
Component.translatable("message.bclib.modmissmatch"),
|
||||
CommonComponents.GUI_PROCEED,
|
||||
ModUtil.getMods(),
|
||||
modVersion));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
protected void showBCLibError(Minecraft client) {
|
||||
BCLib.LOGGER.error("BCLib differs on client and server.");
|
||||
client.setScreen(new WarnBCLibVersionMismatch((download) -> {
|
||||
if (download) {
|
||||
requestBCLibDownload();
|
||||
|
||||
this.onCloseSyncFilesScreen();
|
||||
} else {
|
||||
Minecraft.getInstance()
|
||||
.setScreen(null);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
protected void showSyncFilesScreen(Minecraft client,
|
||||
List<AutoSyncID> files,
|
||||
final List<AutoSyncID.ForDirectFileRequest> filesToRemove) {
|
||||
int configFiles = 0;
|
||||
int singleFiles = 0;
|
||||
int folderFiles = 0;
|
||||
int modFiles = 0;
|
||||
|
||||
for (AutoSyncID aid : files) {
|
||||
if (aid.isConfigFile()) {
|
||||
configFiles++;
|
||||
} else if (aid instanceof AutoSyncID.ForModFileRequest) {
|
||||
modFiles++;
|
||||
} else if (aid instanceof AutoSyncID.ForDirectFileRequest) {
|
||||
folderFiles++;
|
||||
} else {
|
||||
singleFiles++;
|
||||
}
|
||||
}
|
||||
|
||||
client.setScreen(new SyncFilesScreen(modFiles,
|
||||
configFiles,
|
||||
singleFiles,
|
||||
folderFiles,
|
||||
filesToRemove.size(),
|
||||
modVersion,
|
||||
(downloadMods, downloadConfigs, downloadFiles, removeFiles) -> {
|
||||
if (downloadMods || downloadConfigs || downloadFiles) {
|
||||
BCLib.LOGGER.info("Updating local Files:");
|
||||
List<AutoSyncID.WithContentOverride> localChanges = new ArrayList<>(
|
||||
files.toArray().length);
|
||||
List<AutoSyncID> requestFiles = new ArrayList<>(files.toArray().length);
|
||||
|
||||
files.forEach(aid -> {
|
||||
if (aid.isConfigFile() && downloadConfigs) {
|
||||
processOfferedFile(requestFiles, aid);
|
||||
} else if (aid instanceof AutoSyncID.ForModFileRequest && downloadMods) {
|
||||
processOfferedFile(requestFiles, aid);
|
||||
} else if (downloadFiles) {
|
||||
processOfferedFile(requestFiles, aid);
|
||||
}
|
||||
});
|
||||
|
||||
requestFileDownloads(requestFiles);
|
||||
}
|
||||
if (removeFiles) {
|
||||
filesToRemove.forEach(aid -> {
|
||||
BCLib.LOGGER.info(" - " + aid.relFile + " (removing)");
|
||||
aid.relFile.delete();
|
||||
});
|
||||
}
|
||||
|
||||
this.onCloseSyncFilesScreen();
|
||||
}));
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
private void onCloseSyncFilesScreen() {
|
||||
Minecraft.getInstance()
|
||||
.setScreen(ChunkerProgress.getProgressScreen());
|
||||
}
|
||||
|
||||
private void processOfferedFile(List<AutoSyncID> requestFiles, AutoSyncID aid) {
|
||||
if (aid instanceof AutoSyncID.WithContentOverride) {
|
||||
final AutoSyncID.WithContentOverride aidc = (AutoSyncID.WithContentOverride) aid;
|
||||
BCLib.LOGGER.info(" - " + aid + " (updating Content)");
|
||||
|
||||
SendFiles.writeSyncedFile(aid, aidc.contentWrapper.getRawContent(), aidc.localFile);
|
||||
} else {
|
||||
requestFiles.add(aid);
|
||||
BCLib.LOGGER.info(" - " + aid + " (requesting)");
|
||||
}
|
||||
}
|
||||
|
||||
private void requestBCLibDownload() {
|
||||
BCLib.LOGGER.warning("Starting download of BCLib");
|
||||
requestFileDownloads(List.of(new AutoSyncID.ForModFileRequest(BCLib.MOD_ID, bclibVersion)));
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
private void requestFileDownloads(List<AutoSyncID> files) {
|
||||
BCLib.LOGGER.info("Starting download of Files:" + files.size());
|
||||
|
||||
final ProgressScreen progress = new ProgressScreen(null,
|
||||
Component.translatable("title.bclib.filesync.progress"),
|
||||
Component.translatable("message.bclib.filesync.progress"));
|
||||
progress.progressStart(Component.translatable("message.bclib.filesync.progress.stage.empty"));
|
||||
ChunkerProgress.setProgressScreen(progress);
|
||||
|
||||
DataExchangeAPI.send(new RequestFiles(files));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
package org.betterx.bclib.api.dataexchange.handler.autosync;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.api.dataexchange.DataExchangeAPI;
|
||||
import org.betterx.bclib.api.dataexchange.DataHandler;
|
||||
import org.betterx.bclib.api.dataexchange.DataHandlerDescriptor;
|
||||
import org.betterx.bclib.config.Configs;
|
||||
import org.betterx.bclib.util.ModUtil;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* This message is sent once a player enters the world. It initiates a sequence of Messages that will sync files between both
|
||||
* client and server.
|
||||
* <table>
|
||||
* <caption>Description</caption>
|
||||
* <tr>
|
||||
* <th>Server</th>
|
||||
* <th></th>
|
||||
* <th>Client</th>
|
||||
* <th></th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td colspan="4">Player enters World</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td></td>
|
||||
* <td><--</td>
|
||||
* <td>{@link HelloServer}</td>
|
||||
* <td>Sends the current BLib-Version installed on the Client</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@link HelloClient}</td>
|
||||
* <td>--></td>
|
||||
* <td></td>
|
||||
* <td>Sends the current BClIb-Version, the Version of all Plugins and data for all AutpoSync-Files
|
||||
* ({@link DataExchangeAPI#addAutoSyncFile(String, File)} on the Server</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td></td>
|
||||
* <td><--</td>
|
||||
* <td>{@link RequestFiles}</td>
|
||||
* <td>Request missing or out of sync Files from the Server</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@link SendFiles}</td>
|
||||
* <td>--></td>
|
||||
* <td></td>
|
||||
* <td>Send Files from the Server to the Client</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*/
|
||||
public class HelloServer extends DataHandler.FromClient {
|
||||
public static final DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor(new ResourceLocation(BCLib.MOD_ID,
|
||||
"hello_server"),
|
||||
HelloServer::new,
|
||||
true,
|
||||
false);
|
||||
|
||||
protected String bclibVersion = "0.0.0";
|
||||
|
||||
public HelloServer() {
|
||||
super(DESCRIPTOR.IDENTIFIER);
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
@Override
|
||||
protected boolean prepareDataOnClient() {
|
||||
if (!Configs.CLIENT_CONFIG.isAllowingAutoSync()) {
|
||||
BCLib.LOGGER.info("Auto-Sync was disabled on the client.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
@Override
|
||||
protected void serializeDataOnClient(FriendlyByteBuf buf) {
|
||||
BCLib.LOGGER.info("Sending hello to server.");
|
||||
buf.writeInt(ModUtil.convertModVersion(HelloClient.getBCLibVersion()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deserializeIncomingDataOnServer(FriendlyByteBuf buf, Player player, PacketSender responseSender) {
|
||||
bclibVersion = ModUtil.convertModVersion(buf.readInt());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void runOnServerGameThread(MinecraftServer server, Player player) {
|
||||
if (!Configs.SERVER_CONFIG.isAllowingAutoSync()) {
|
||||
BCLib.LOGGER.info("Auto-Sync was disabled on the server.");
|
||||
return;
|
||||
}
|
||||
|
||||
String localBclibVersion = HelloClient.getBCLibVersion();
|
||||
BCLib.LOGGER.info("Received Hello from Client. (server=" + localBclibVersion + ", client=" + bclibVersion + ")");
|
||||
|
||||
if (!server.isPublished()) {
|
||||
BCLib.LOGGER.info("Auto-Sync is disabled for Singleplayer worlds.");
|
||||
return;
|
||||
}
|
||||
|
||||
reply(new HelloClient(), server);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
package org.betterx.bclib.api.dataexchange.handler.autosync;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.api.dataexchange.DataHandler;
|
||||
import org.betterx.bclib.api.dataexchange.DataHandlerDescriptor;
|
||||
import org.betterx.bclib.config.Configs;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class RequestFiles extends DataHandler.FromClient {
|
||||
public static final DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor(new ResourceLocation(BCLib.MOD_ID,
|
||||
"request_files"),
|
||||
RequestFiles::new,
|
||||
false,
|
||||
false);
|
||||
static String currentToken = "";
|
||||
|
||||
protected List<AutoSyncID> files;
|
||||
|
||||
private RequestFiles() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public RequestFiles(List<AutoSyncID> files) {
|
||||
super(DESCRIPTOR.IDENTIFIER);
|
||||
this.files = files;
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
@Override
|
||||
protected boolean prepareDataOnClient() {
|
||||
if (!Configs.CLIENT_CONFIG.isAllowingAutoSync()) {
|
||||
BCLib.LOGGER.info("Auto-Sync was disabled on the client.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
@Override
|
||||
protected void serializeDataOnClient(FriendlyByteBuf buf) {
|
||||
newToken();
|
||||
writeString(buf, currentToken);
|
||||
|
||||
buf.writeInt(files.size());
|
||||
|
||||
for (AutoSyncID a : files) {
|
||||
a.serializeData(buf);
|
||||
}
|
||||
}
|
||||
|
||||
String receivedToken = "";
|
||||
|
||||
@Override
|
||||
protected void deserializeIncomingDataOnServer(FriendlyByteBuf buf, Player player, PacketSender responseSender) {
|
||||
receivedToken = readString(buf);
|
||||
int size = buf.readInt();
|
||||
files = new ArrayList<>(size);
|
||||
|
||||
BCLib.LOGGER.info("Client requested " + size + " Files:");
|
||||
for (int i = 0; i < size; i++) {
|
||||
AutoSyncID asid = AutoSyncID.deserializeData(buf);
|
||||
files.add(asid);
|
||||
BCLib.LOGGER.info(" - " + asid);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void runOnServerGameThread(MinecraftServer server, Player player) {
|
||||
if (!Configs.SERVER_CONFIG.isAllowingAutoSync()) {
|
||||
BCLib.LOGGER.info("Auto-Sync was disabled on the server.");
|
||||
return;
|
||||
}
|
||||
|
||||
List<AutoFileSyncEntry> syncEntries = files.stream()
|
||||
.map(asid -> AutoFileSyncEntry.findMatching(asid))
|
||||
.filter(e -> e != null)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
reply(new SendFiles(syncEntries, receivedToken), server);
|
||||
}
|
||||
|
||||
public static void newToken() {
|
||||
currentToken = UUID.randomUUID()
|
||||
.toString();
|
||||
}
|
||||
|
||||
static {
|
||||
newToken();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,219 @@
|
|||
package org.betterx.bclib.api.dataexchange.handler.autosync;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.api.dataexchange.DataHandler;
|
||||
import org.betterx.bclib.api.dataexchange.DataHandlerDescriptor;
|
||||
import org.betterx.bclib.config.Configs;
|
||||
import org.betterx.bclib.gui.screens.ConfirmRestartScreen;
|
||||
import org.betterx.bclib.util.Pair;
|
||||
import org.betterx.bclib.util.PathUtil;
|
||||
import org.betterx.bclib.util.Triple;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class SendFiles extends DataHandler.FromServer {
|
||||
public static final DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor(new ResourceLocation(BCLib.MOD_ID,
|
||||
"send_files"),
|
||||
SendFiles::new,
|
||||
false,
|
||||
false);
|
||||
|
||||
protected List<AutoFileSyncEntry> files;
|
||||
private String token;
|
||||
|
||||
public SendFiles() {
|
||||
this(null, "");
|
||||
}
|
||||
|
||||
public SendFiles(List<AutoFileSyncEntry> files, String token) {
|
||||
super(DESCRIPTOR.IDENTIFIER);
|
||||
this.files = files;
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean prepareDataOnServer() {
|
||||
if (!Configs.SERVER_CONFIG.isAllowingAutoSync()) {
|
||||
BCLib.LOGGER.info("Auto-Sync was disabled on the server.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void serializeDataOnServer(FriendlyByteBuf buf) {
|
||||
List<AutoFileSyncEntry> existingFiles = files.stream()
|
||||
.filter(e -> e != null && e.fileName != null && e.fileName.exists())
|
||||
.collect(Collectors.toList());
|
||||
/*
|
||||
//this will try to send a file that was not registered or requested by the client
|
||||
existingFiles.add(new AutoFileSyncEntry("none", new File("D:\\MinecraftPlugins\\BetterNether\\run\\server.properties"),true,(a, b, content) -> {
|
||||
System.out.println("Got Content:" + content.length);
|
||||
return true;
|
||||
}));*/
|
||||
|
||||
/*//this will try to send a folder-file that was not registered or requested by the client
|
||||
existingFiles.add(new AutoFileSyncEntry.ForDirectFileRequest(DataExchange.SYNC_FOLDER.folderID, new File("test.json"), DataExchange.SYNC_FOLDER.mapAbsolute("test.json").toFile()));*/
|
||||
|
||||
/*//this will try to send a folder-file that was not registered or requested by the client and is outside the base-folder
|
||||
existingFiles.add(new AutoFileSyncEntry.ForDirectFileRequest(DataExchange.SYNC_FOLDER.folderID, new File("../breakout.json"), DataExchange.SYNC_FOLDER.mapAbsolute("../breakout.json").toFile()));*/
|
||||
|
||||
|
||||
writeString(buf, token);
|
||||
buf.writeInt(existingFiles.size());
|
||||
|
||||
BCLib.LOGGER.info("Sending " + existingFiles.size() + " Files to Client:");
|
||||
for (AutoFileSyncEntry entry : existingFiles) {
|
||||
int length = entry.serializeContent(buf);
|
||||
BCLib.LOGGER.info(" - " + entry + " (" + PathUtil.humanReadableFileSize(length) + ")");
|
||||
}
|
||||
}
|
||||
|
||||
private List<Pair<AutoFileSyncEntry, byte[]>> receivedFiles;
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
@Override
|
||||
protected void deserializeIncomingDataOnClient(FriendlyByteBuf buf, PacketSender responseSender) {
|
||||
if (Configs.CLIENT_CONFIG.isAcceptingConfigs() || Configs.CLIENT_CONFIG.isAcceptingFiles() || Configs.CLIENT_CONFIG.isAcceptingMods()) {
|
||||
token = readString(buf);
|
||||
if (!token.equals(RequestFiles.currentToken)) {
|
||||
RequestFiles.newToken();
|
||||
BCLib.LOGGER.error("Unrequested File Transfer!");
|
||||
receivedFiles = new ArrayList<>(0);
|
||||
return;
|
||||
}
|
||||
RequestFiles.newToken();
|
||||
|
||||
int size = buf.readInt();
|
||||
receivedFiles = new ArrayList<>(size);
|
||||
BCLib.LOGGER.info("Server sent " + size + " Files:");
|
||||
for (int i = 0; i < size; i++) {
|
||||
Triple<AutoFileSyncEntry, byte[], AutoSyncID> p = AutoFileSyncEntry.deserializeContent(buf);
|
||||
if (p.first != null) {
|
||||
final String type;
|
||||
if (p.first.isConfigFile() && Configs.CLIENT_CONFIG.isAcceptingConfigs()) {
|
||||
receivedFiles.add(p);
|
||||
type = "Accepted Config ";
|
||||
} else if (p.first instanceof AutoFileSyncEntry.ForModFileRequest && Configs.CLIENT_CONFIG.isAcceptingMods()) {
|
||||
receivedFiles.add(p);
|
||||
type = "Accepted Mod ";
|
||||
} else if (Configs.CLIENT_CONFIG.isAcceptingFiles()) {
|
||||
receivedFiles.add(p);
|
||||
type = "Accepted File ";
|
||||
} else {
|
||||
type = "Ignoring ";
|
||||
}
|
||||
BCLib.LOGGER.info(" - " + type + p.first + " (" + PathUtil.humanReadableFileSize(p.second.length) + ")");
|
||||
} else {
|
||||
BCLib.LOGGER.error(" - Failed to receive File " + p.third + ", possibly sent from a Mod that is not installed on the client.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
@Override
|
||||
protected void runOnClientGameThread(Minecraft client) {
|
||||
if (Configs.CLIENT_CONFIG.isAcceptingConfigs() || Configs.CLIENT_CONFIG.isAcceptingFiles() || Configs.CLIENT_CONFIG.isAcceptingMods()) {
|
||||
BCLib.LOGGER.info("Writing Files:");
|
||||
|
||||
for (Pair<AutoFileSyncEntry, byte[]> entry : receivedFiles) {
|
||||
final AutoFileSyncEntry e = entry.first;
|
||||
final byte[] data = entry.second;
|
||||
|
||||
writeSyncedFile(e, data, e.fileName);
|
||||
}
|
||||
|
||||
showConfirmRestart(client);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
static void writeSyncedFile(AutoSyncID e, byte[] data, File fileName) {
|
||||
if (fileName != null && !PathUtil.isChildOf(PathUtil.GAME_FOLDER, fileName.toPath())) {
|
||||
BCLib.LOGGER.error(fileName + " is not within game folder " + PathUtil.GAME_FOLDER);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!PathUtil.MOD_BAK_FOLDER.toFile().exists()) {
|
||||
PathUtil.MOD_BAK_FOLDER.toFile().mkdirs();
|
||||
}
|
||||
|
||||
Path path = fileName != null ? fileName.toPath() : null;
|
||||
Path removeAfter = null;
|
||||
if (e instanceof AutoFileSyncEntry.ForModFileRequest mase) {
|
||||
removeAfter = path;
|
||||
int count = 0;
|
||||
final String prefix = "_bclib_synced_";
|
||||
String name = prefix + mase.modID + "_" + mase.version.replace(".", "_") + ".jar";
|
||||
do {
|
||||
if (path != null) {
|
||||
//move to the same directory as the existing Mod
|
||||
path = path.getParent()
|
||||
.resolve(name);
|
||||
} else {
|
||||
//move to the default mode location
|
||||
path = PathUtil.MOD_FOLDER.resolve(name);
|
||||
}
|
||||
count++;
|
||||
name = prefix + mase.modID + "_" + mase.version.replace(".", "_") + "__" + String.format("%03d",
|
||||
count) + ".jar";
|
||||
} while (path.toFile().exists());
|
||||
}
|
||||
|
||||
BCLib.LOGGER.info(" - Writing " + path + " (" + PathUtil.humanReadableFileSize(data.length) + ")");
|
||||
try {
|
||||
final File parentFile = path.getParent()
|
||||
.toFile();
|
||||
if (!parentFile.exists()) {
|
||||
parentFile.mkdirs();
|
||||
}
|
||||
Files.write(path, data);
|
||||
if (removeAfter != null) {
|
||||
final String bakFileName = removeAfter.toFile().getName();
|
||||
String collisionFreeName = bakFileName;
|
||||
Path targetPath;
|
||||
int count = 0;
|
||||
do {
|
||||
targetPath = PathUtil.MOD_BAK_FOLDER.resolve(collisionFreeName);
|
||||
count++;
|
||||
collisionFreeName = String.format("%03d", count) + "_" + bakFileName;
|
||||
} while (targetPath.toFile().exists());
|
||||
|
||||
BCLib.LOGGER.info(" - Moving " + removeAfter + " to " + targetPath);
|
||||
removeAfter.toFile().renameTo(targetPath.toFile());
|
||||
}
|
||||
AutoSync.didReceiveFile(e, fileName);
|
||||
|
||||
|
||||
} catch (IOException ioException) {
|
||||
BCLib.LOGGER.error(" --> Writing " + fileName + " failed: " + ioException);
|
||||
}
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
protected void showConfirmRestart(Minecraft client) {
|
||||
client.setScreen(new ConfirmRestartScreen(() -> {
|
||||
Minecraft.getInstance()
|
||||
.setScreen(null);
|
||||
client.stop();
|
||||
}));
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,214 @@
|
|||
package org.betterx.bclib.api.dataexchange.handler.autosync;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.api.dataexchange.DataHandler;
|
||||
import org.betterx.bclib.api.dataexchange.FileHash;
|
||||
import org.betterx.bclib.api.dataexchange.handler.autosync.AutoSyncID.ForDirectFileRequest;
|
||||
import org.betterx.bclib.config.Configs;
|
||||
import org.betterx.bclib.util.PathUtil;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class SyncFolderDescriptor {
|
||||
static class SubFile {
|
||||
public final String relPath;
|
||||
public final FileHash hash;
|
||||
|
||||
|
||||
SubFile(String relPath, FileHash hash) {
|
||||
this.relPath = relPath;
|
||||
this.hash = hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return relPath;
|
||||
}
|
||||
|
||||
public void serialize(FriendlyByteBuf buf) {
|
||||
DataHandler.writeString(buf, relPath);
|
||||
hash.serialize(buf);
|
||||
}
|
||||
|
||||
public static SubFile deserialize(FriendlyByteBuf buf) {
|
||||
final String relPath = DataHandler.readString(buf);
|
||||
FileHash hash = FileHash.deserialize(buf);
|
||||
return new SubFile(relPath, hash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o instanceof String) return relPath.equals(o);
|
||||
if (!(o instanceof SubFile)) return false;
|
||||
SubFile subFile = (SubFile) o;
|
||||
return relPath.equals(subFile.relPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return relPath.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public final String folderID;
|
||||
public final boolean removeAdditionalFiles;
|
||||
@NotNull
|
||||
public final Path localFolder;
|
||||
|
||||
private List<SubFile> fileCache;
|
||||
|
||||
public SyncFolderDescriptor(String folderID, Path localFolder, boolean removeAdditionalFiles) {
|
||||
this.removeAdditionalFiles = removeAdditionalFiles;
|
||||
this.folderID = folderID;
|
||||
this.localFolder = localFolder;
|
||||
fileCache = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SyncFolderDescriptor{" + "folderID='" + folderID + '\'' + ", removeAdditionalFiles=" + removeAdditionalFiles + ", localFolder=" + localFolder + ", files=" + (
|
||||
fileCache == null
|
||||
? "?"
|
||||
: fileCache.size()) + "}";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o instanceof String) {
|
||||
return folderID.equals(o);
|
||||
}
|
||||
if (o instanceof ForDirectFileRequest) {
|
||||
return folderID.equals(((ForDirectFileRequest) o).uniqueID);
|
||||
}
|
||||
if (!(o instanceof SyncFolderDescriptor)) return false;
|
||||
SyncFolderDescriptor that = (SyncFolderDescriptor) o;
|
||||
return folderID.equals(that.folderID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return folderID.hashCode();
|
||||
}
|
||||
|
||||
public int fileCount() {
|
||||
return fileCache == null ? 0 : fileCache.size();
|
||||
}
|
||||
|
||||
public void invalidateCache() {
|
||||
fileCache = null;
|
||||
}
|
||||
|
||||
public void loadCache() {
|
||||
if (fileCache == null) {
|
||||
fileCache = new ArrayList<>(8);
|
||||
PathUtil.fileWalker(localFolder.toFile(), p -> fileCache.add(new SubFile(localFolder.relativize(p)
|
||||
.toString(),
|
||||
FileHash.create(p.toFile()))));
|
||||
|
||||
/*//this tests if we can trick the system to load files that are not beneath the base-folder
|
||||
if (!BCLib.isClient()) {
|
||||
fileCache.add(new SubFile("../breakout.json", FileHash.create(mapAbsolute("../breakout.json").toFile())));
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
public void serialize(FriendlyByteBuf buf) {
|
||||
final boolean debugHashes = Configs.CLIENT_CONFIG.getBoolean(AutoSync.SYNC_CATEGORY, "debugHashes", false);
|
||||
loadCache();
|
||||
|
||||
DataHandler.writeString(buf, folderID);
|
||||
buf.writeBoolean(removeAdditionalFiles);
|
||||
buf.writeInt(fileCache.size());
|
||||
fileCache.forEach(fl -> {
|
||||
BCLib.LOGGER.info(" - " + fl.relPath);
|
||||
if (debugHashes) {
|
||||
BCLib.LOGGER.info(" " + fl.hash);
|
||||
}
|
||||
fl.serialize(buf);
|
||||
});
|
||||
}
|
||||
|
||||
public static SyncFolderDescriptor deserialize(FriendlyByteBuf buf) {
|
||||
final String folderID = DataHandler.readString(buf);
|
||||
final boolean remAddFiles = buf.readBoolean();
|
||||
final int count = buf.readInt();
|
||||
SyncFolderDescriptor localDescriptor = AutoSync.getSyncFolderDescriptor(folderID);
|
||||
|
||||
final SyncFolderDescriptor desc;
|
||||
if (localDescriptor != null) {
|
||||
desc = new SyncFolderDescriptor(folderID,
|
||||
localDescriptor.localFolder,
|
||||
localDescriptor.removeAdditionalFiles && remAddFiles);
|
||||
desc.fileCache = new ArrayList<>(count);
|
||||
} else {
|
||||
BCLib.LOGGER.warning(BCLib.isClient()
|
||||
? "Client"
|
||||
: "Server" + " does not know Sync-Folder ID '" + folderID + "'");
|
||||
desc = null;
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
SubFile relPath = SubFile.deserialize(buf);
|
||||
if (desc != null) desc.fileCache.add(relPath);
|
||||
}
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
//Note: make sure loadCache was called before using this
|
||||
boolean hasRelativeFile(String relFile) {
|
||||
return fileCache.stream()
|
||||
.filter(sf -> sf.equals(relFile))
|
||||
.findFirst()
|
||||
.isPresent();
|
||||
}
|
||||
|
||||
//Note: make sure loadCache was called before using this
|
||||
boolean hasRelativeFile(SubFile subFile) {
|
||||
return hasRelativeFile(subFile.relPath);
|
||||
}
|
||||
|
||||
//Note: make sure loadCache was called before using this
|
||||
SubFile getLocalSubFile(String relPath) {
|
||||
return fileCache.stream()
|
||||
.filter(sf -> sf.relPath.equals(relPath))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
Stream<SubFile> relativeFilesStream() {
|
||||
loadCache();
|
||||
return fileCache.stream();
|
||||
}
|
||||
|
||||
public Path mapAbsolute(String relPath) {
|
||||
return this.localFolder.resolve(relPath)
|
||||
.normalize();
|
||||
}
|
||||
|
||||
public Path mapAbsolute(SubFile subFile) {
|
||||
return this.localFolder.resolve(subFile.relPath)
|
||||
.normalize();
|
||||
}
|
||||
|
||||
public boolean acceptChildElements(Path absPath) {
|
||||
return PathUtil.isChildOf(this.localFolder, absPath);
|
||||
}
|
||||
|
||||
public boolean acceptChildElements(SubFile subFile) {
|
||||
return acceptChildElements(mapAbsolute(subFile));
|
||||
}
|
||||
|
||||
public boolean discardChildElements(SubFile subFile) {
|
||||
return !acceptChildElements(subFile);
|
||||
}
|
||||
}
|
632
src/main/java/org/betterx/bclib/api/datafixer/DataFixerAPI.java
Normal file
632
src/main/java/org/betterx/bclib/api/datafixer/DataFixerAPI.java
Normal file
|
@ -0,0 +1,632 @@
|
|||
package org.betterx.bclib.api.datafixer;
|
||||
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.components.toasts.SystemToast;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.client.gui.screens.worldselection.EditWorldScreen;
|
||||
import net.minecraft.nbt.*;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.chunk.storage.RegionFile;
|
||||
import net.minecraft.world.level.storage.LevelResource;
|
||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||
import net.minecraft.world.level.storage.LevelStorageSource.LevelStorageAccess;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.api.WorldDataAPI;
|
||||
import org.betterx.bclib.config.Configs;
|
||||
import org.betterx.bclib.gui.screens.AtomicProgressListener;
|
||||
import org.betterx.bclib.gui.screens.ConfirmFixScreen;
|
||||
import org.betterx.bclib.gui.screens.LevelFixErrorScreen;
|
||||
import org.betterx.bclib.gui.screens.LevelFixErrorScreen.Listener;
|
||||
import org.betterx.bclib.gui.screens.ProgressScreen;
|
||||
import org.betterx.bclib.util.Logger;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.zip.ZipException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* API to manage Patches that need to get applied to a world
|
||||
*/
|
||||
public class DataFixerAPI {
|
||||
static final Logger LOGGER = new Logger("DataFixerAPI");
|
||||
|
||||
static class State {
|
||||
public boolean didFail = false;
|
||||
protected ArrayList<String> errors = new ArrayList<>();
|
||||
|
||||
public void addError(String s) {
|
||||
errors.add(s);
|
||||
}
|
||||
|
||||
public boolean hasError() {
|
||||
return errors.size() > 0;
|
||||
}
|
||||
|
||||
public String getErrorMessage() {
|
||||
return errors.stream().reduce("", (a, b) -> a + " - " + b + "\n");
|
||||
}
|
||||
|
||||
public String[] getErrorMessages() {
|
||||
String[] res = new String[errors.size()];
|
||||
return errors.toArray(res);
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Callback {
|
||||
void call();
|
||||
}
|
||||
|
||||
private static boolean wrapCall(LevelStorageSource levelSource,
|
||||
String levelID,
|
||||
Function<LevelStorageAccess, Boolean> runWithLevel) {
|
||||
LevelStorageSource.LevelStorageAccess levelStorageAccess;
|
||||
try {
|
||||
levelStorageAccess = levelSource.createAccess(levelID);
|
||||
} catch (IOException e) {
|
||||
BCLib.LOGGER.warning("Failed to read level {} data", levelID, e);
|
||||
SystemToast.onWorldAccessFailure(Minecraft.getInstance(), levelID);
|
||||
Minecraft.getInstance().setScreen(null);
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean returnValue = runWithLevel.apply(levelStorageAccess);
|
||||
|
||||
try {
|
||||
levelStorageAccess.close();
|
||||
} catch (IOException e) {
|
||||
BCLib.LOGGER.warning("Failed to unlock access to level {}", levelID, e);
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will apply necessary Patches to the world.
|
||||
*
|
||||
* @param levelSource The SourceStorage for this Minecraft instance, You can get this using
|
||||
* {@code Minecraft.getInstance().getLevelSource()}
|
||||
* @param levelID The ID of the Level you want to patch
|
||||
* @param showUI {@code true}, if you want to present the user with a Screen that offers to backup the world
|
||||
* before applying the patches
|
||||
* @param onResume When this method retursn {@code true}, this function will be called when the world is ready
|
||||
* @return {@code true} if the UI was displayed. The UI is only displayed if {@code showUI} was {@code true} and
|
||||
* patches were enabled in the config and the Guardian did find any patches that need to be applied to the world.
|
||||
*/
|
||||
public static boolean fixData(LevelStorageSource levelSource,
|
||||
String levelID,
|
||||
boolean showUI,
|
||||
Consumer<Boolean> onResume) {
|
||||
return wrapCall(levelSource, levelID, (levelStorageAccess) -> fixData(levelStorageAccess, showUI, onResume));
|
||||
}
|
||||
|
||||
/**
|
||||
* Will apply necessary Patches to the world.
|
||||
*
|
||||
* @param levelStorageAccess The access class of the level you want to patch
|
||||
* @param showUI {@code true}, if you want to present the user with a Screen that offers to backup the world
|
||||
* before applying the patches
|
||||
* @param onResume When this method retursn {@code true}, this function will be called when the world is ready
|
||||
* @return {@code true} if the UI was displayed. The UI is only displayed if {@code showUI} was {@code true} and
|
||||
* patches were enabled in the config and the Guardian did find any patches that need to be applied to the world.
|
||||
*/
|
||||
public static boolean fixData(LevelStorageSource.LevelStorageAccess levelStorageAccess,
|
||||
boolean showUI,
|
||||
Consumer<Boolean> onResume) {
|
||||
File levelPath = levelStorageAccess.getLevelPath(LevelResource.ROOT).toFile();
|
||||
File levelDat = levelStorageAccess.getLevelPath(LevelResource.LEVEL_DATA_FILE).toFile();
|
||||
boolean newWorld = false;
|
||||
if (!levelDat.exists()) {
|
||||
BCLib.LOGGER.info("Creating a new World, no fixes needed");
|
||||
newWorld = true;
|
||||
}
|
||||
|
||||
initializeWorldData(levelPath, newWorld);
|
||||
if (newWorld) return false;
|
||||
|
||||
return fixData(levelPath, levelStorageAccess.getLevelId(), showUI, onResume);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the DataStorage for this world. If the world is new, the patch registry is initialized to the
|
||||
* current versions of the plugins.
|
||||
* <p>
|
||||
* This implementation will create a new {@link LevelStorageAccess} and call {@link #initializeWorldData(File, boolean)}
|
||||
* using the provided root path.
|
||||
*
|
||||
* @param levelSource The SourceStorage for this Minecraft instance, You can get this using
|
||||
* {@code Minecraft.getInstance().getLevelSource()}
|
||||
* @param levelID The ID of the Level you want to patch
|
||||
* @param newWorld {@code true} if this is a fresh world
|
||||
*/
|
||||
public static void initializeWorldData(LevelStorageSource levelSource, String levelID, boolean newWorld) {
|
||||
wrapCall(levelSource, levelID, (levelStorageAccess) -> {
|
||||
initializeWorldData(levelStorageAccess, newWorld);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the DataStorage for this world. If the world is new, the patch registry is initialized to the
|
||||
* current versions of the plugins.
|
||||
*
|
||||
* @param access levelAccess for the worldd
|
||||
* @param newWorld {@code true} if this is a fresh world
|
||||
*/
|
||||
public static void initializeWorldData(LevelStorageAccess access, boolean newWorld) {
|
||||
initializeWorldData(access.getLevelPath(LevelResource.ROOT).toFile(), newWorld);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the DataStorage for this world. If the world is new, the patch registry is initialized to the
|
||||
* current versions of the plugins.
|
||||
*
|
||||
* @param levelBaseDir Folder of the world
|
||||
* @param newWorld {@code true} if this is a fresh world
|
||||
*/
|
||||
public static void initializeWorldData(File levelBaseDir, boolean newWorld) {
|
||||
WorldDataAPI.load(new File(levelBaseDir, "data"));
|
||||
|
||||
if (newWorld) {
|
||||
getMigrationProfile().markApplied();
|
||||
WorldDataAPI.saveFile(BCLib.MOD_ID);
|
||||
}
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
private static AtomicProgressListener showProgressScreen() {
|
||||
ProgressScreen ps = new ProgressScreen(Minecraft.getInstance().screen,
|
||||
Component.translatable("title.bclib.datafixer.progress"),
|
||||
Component.translatable("message.bclib.datafixer.progress"));
|
||||
Minecraft.getInstance().setScreen(ps);
|
||||
return ps;
|
||||
}
|
||||
|
||||
private static boolean fixData(File dir, String levelID, boolean showUI, Consumer<Boolean> onResume) {
|
||||
MigrationProfile profile = loadProfileIfNeeded(dir);
|
||||
|
||||
BiConsumer<Boolean, Boolean> runFixes = (createBackup, applyFixes) -> {
|
||||
final AtomicProgressListener progress;
|
||||
if (applyFixes) {
|
||||
if (showUI) {
|
||||
progress = showProgressScreen();
|
||||
} else {
|
||||
progress = new AtomicProgressListener() {
|
||||
private long timeStamp = Util.getMillis();
|
||||
private AtomicInteger counter = new AtomicInteger(0);
|
||||
|
||||
@Override
|
||||
public void incAtomic(int maxProgress) {
|
||||
int percentage = (100 * counter.incrementAndGet()) / maxProgress;
|
||||
if (Util.getMillis() - this.timeStamp >= 1000L) {
|
||||
this.timeStamp = Util.getMillis();
|
||||
BCLib.LOGGER.info("Patching... {}%", percentage);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetAtomic() {
|
||||
counter = new AtomicInteger(0);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
}
|
||||
|
||||
public void progressStage(Component component) {
|
||||
BCLib.LOGGER.info("Patcher Stage... {}%", component.getString());
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
progress = null;
|
||||
}
|
||||
|
||||
Supplier<State> runner = () -> {
|
||||
if (createBackup) {
|
||||
progress.progressStage(Component.translatable("message.bclib.datafixer.progress.waitbackup"));
|
||||
EditWorldScreen.makeBackupAndShowToast(Minecraft.getInstance().getLevelSource(), levelID);
|
||||
}
|
||||
|
||||
if (applyFixes) {
|
||||
return runDataFixes(dir, profile, progress);
|
||||
}
|
||||
|
||||
return new State();
|
||||
};
|
||||
|
||||
if (showUI) {
|
||||
Thread fixerThread = new Thread(() -> {
|
||||
final State state = runner.get();
|
||||
|
||||
Minecraft.getInstance()
|
||||
.execute(() -> {
|
||||
if (profile != null && showUI) {
|
||||
//something went wrong, show the user our error
|
||||
if (state.didFail || state.hasError()) {
|
||||
showLevelFixErrorScreen(state, (markFixed) -> {
|
||||
if (markFixed) {
|
||||
profile.markApplied();
|
||||
}
|
||||
onResume.accept(applyFixes);
|
||||
});
|
||||
} else {
|
||||
onResume.accept(applyFixes);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
fixerThread.start();
|
||||
} else {
|
||||
State state = runner.get();
|
||||
if (state.hasError()) {
|
||||
LOGGER.error("There were Errors while fixing the Level:");
|
||||
LOGGER.error(state.getErrorMessage());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//we have some migrations
|
||||
if (profile != null) {
|
||||
//display the confirm UI.
|
||||
if (showUI) {
|
||||
showBackupWarning(levelID, runFixes);
|
||||
return true;
|
||||
} else {
|
||||
BCLib.LOGGER.warning("Applying Fixes on Level");
|
||||
runFixes.accept(false, true);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
private static void showLevelFixErrorScreen(State state, Listener onContinue) {
|
||||
Minecraft.getInstance()
|
||||
.setScreen(new LevelFixErrorScreen(Minecraft.getInstance().screen,
|
||||
state.getErrorMessages(),
|
||||
onContinue));
|
||||
}
|
||||
|
||||
private static MigrationProfile loadProfileIfNeeded(File levelBaseDir) {
|
||||
if (!Configs.MAIN_CONFIG.applyPatches()) {
|
||||
LOGGER.info("World Patches are disabled");
|
||||
return null;
|
||||
}
|
||||
|
||||
MigrationProfile profile = getMigrationProfile();
|
||||
profile.runPrePatches(levelBaseDir);
|
||||
|
||||
if (!profile.hasAnyFixes()) {
|
||||
LOGGER.info("Everything up to date");
|
||||
return null;
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static MigrationProfile getMigrationProfile() {
|
||||
final CompoundTag patchConfig = WorldDataAPI.getCompoundTag(BCLib.MOD_ID, Configs.MAIN_PATCH_CATEGORY);
|
||||
MigrationProfile profile = Patch.createMigrationData(patchConfig);
|
||||
return profile;
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
static void showBackupWarning(String levelID, BiConsumer<Boolean, Boolean> whenFinished) {
|
||||
Minecraft.getInstance().setScreen(new ConfirmFixScreen(null, whenFinished::accept));
|
||||
}
|
||||
|
||||
private static State runDataFixes(File dir, MigrationProfile profile, AtomicProgressListener progress) {
|
||||
State state = new State();
|
||||
progress.resetAtomic();
|
||||
|
||||
progress.progressStage(Component.translatable("message.bclib.datafixer.progress.reading"));
|
||||
List<File> players = getAllPlayers(dir);
|
||||
List<File> regions = getAllRegions(dir, null);
|
||||
final int maxProgress = players.size() + regions.size() + 4;
|
||||
progress.incAtomic(maxProgress);
|
||||
|
||||
progress.progressStage(Component.translatable("message.bclib.datafixer.progress.players"));
|
||||
players.parallelStream().forEach((file) -> {
|
||||
fixPlayer(profile, state, file);
|
||||
progress.incAtomic(maxProgress);
|
||||
});
|
||||
|
||||
progress.progressStage(Component.translatable("message.bclib.datafixer.progress.level"));
|
||||
fixLevel(profile, state, dir);
|
||||
progress.incAtomic(maxProgress);
|
||||
|
||||
progress.progressStage(Component.translatable("message.bclib.datafixer.progress.worlddata"));
|
||||
try {
|
||||
profile.patchWorldData();
|
||||
} catch (PatchDidiFailException e) {
|
||||
state.didFail = true;
|
||||
state.addError("Failed fixing worldconfig (" + e.getMessage() + ")");
|
||||
BCLib.LOGGER.error(e.getMessage());
|
||||
}
|
||||
progress.incAtomic(maxProgress);
|
||||
|
||||
progress.progressStage(Component.translatable("message.bclib.datafixer.progress.regions"));
|
||||
regions.parallelStream().forEach((file) -> {
|
||||
fixRegion(profile, state, file);
|
||||
progress.incAtomic(maxProgress);
|
||||
});
|
||||
|
||||
if (!state.didFail) {
|
||||
progress.progressStage(Component.translatable("message.bclib.datafixer.progress.saving"));
|
||||
profile.markApplied();
|
||||
WorldDataAPI.saveFile(BCLib.MOD_ID);
|
||||
}
|
||||
progress.incAtomic(maxProgress);
|
||||
|
||||
progress.stop();
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
private static void fixLevel(MigrationProfile profile, State state, File levelBaseDir) {
|
||||
try {
|
||||
LOGGER.info("Inspecting level.dat in " + levelBaseDir);
|
||||
|
||||
//load the level (could already contain patches applied by patchLevelDat)
|
||||
CompoundTag level = profile.getLevelDat(levelBaseDir);
|
||||
boolean[] changed = {profile.isLevelDatChanged()};
|
||||
|
||||
if (profile.getPrePatchException() != null) {
|
||||
throw profile.getPrePatchException();
|
||||
}
|
||||
|
||||
if (level.contains("Data")) {
|
||||
CompoundTag dataTag = (CompoundTag) level.get("Data");
|
||||
if (dataTag.contains("Player")) {
|
||||
CompoundTag player = (CompoundTag) dataTag.get("Player");
|
||||
fixPlayerNbt(player, changed, profile);
|
||||
}
|
||||
}
|
||||
|
||||
if (changed[0]) {
|
||||
LOGGER.warning("Writing '{}'", profile.getLevelDatFile());
|
||||
NbtIo.writeCompressed(level, profile.getLevelDatFile());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
BCLib.LOGGER.error("Failed fixing Level-Data.");
|
||||
state.addError("Failed fixing Level-Data in level.dat (" + e.getMessage() + ")");
|
||||
state.didFail = true;
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static void fixPlayer(MigrationProfile data, State state, File file) {
|
||||
try {
|
||||
LOGGER.info("Inspecting " + file);
|
||||
|
||||
CompoundTag player = readNbt(file);
|
||||
boolean[] changed = {false};
|
||||
fixPlayerNbt(player, changed, data);
|
||||
|
||||
if (changed[0]) {
|
||||
LOGGER.warning("Writing '{}'", file);
|
||||
NbtIo.writeCompressed(player, file);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
BCLib.LOGGER.error("Failed fixing Player-Data.");
|
||||
state.addError("Failed fixing Player-Data in " + file.getName() + " (" + e.getMessage() + ")");
|
||||
state.didFail = true;
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static void fixPlayerNbt(CompoundTag player, boolean[] changed, MigrationProfile data) {
|
||||
//Checking Inventory
|
||||
ListTag inventory = player.getList("Inventory", Tag.TAG_COMPOUND);
|
||||
fixItemArrayWithID(inventory, changed, data, true);
|
||||
|
||||
//Checking EnderChest
|
||||
ListTag enderitems = player.getList("EnderItems", Tag.TAG_COMPOUND);
|
||||
fixItemArrayWithID(enderitems, changed, data, true);
|
||||
|
||||
//Checking ReceipBook
|
||||
if (player.contains("recipeBook")) {
|
||||
CompoundTag recipeBook = player.getCompound("recipeBook");
|
||||
changed[0] |= fixStringIDList(recipeBook, "recipes", data);
|
||||
changed[0] |= fixStringIDList(recipeBook, "toBeDisplayed", data);
|
||||
}
|
||||
}
|
||||
|
||||
static boolean fixStringIDList(CompoundTag root, String name, MigrationProfile data) {
|
||||
boolean _changed = false;
|
||||
if (root.contains(name)) {
|
||||
ListTag items = root.getList(name, Tag.TAG_STRING);
|
||||
ListTag newItems = new ListTag();
|
||||
|
||||
for (Tag tag : items) {
|
||||
final StringTag str = (StringTag) tag;
|
||||
final String replace = data.replaceStringFromIDs(str.getAsString());
|
||||
if (replace != null) {
|
||||
_changed = true;
|
||||
newItems.add(StringTag.valueOf(replace));
|
||||
} else {
|
||||
newItems.add(tag);
|
||||
}
|
||||
}
|
||||
if (_changed) {
|
||||
root.put(name, newItems);
|
||||
}
|
||||
}
|
||||
return _changed;
|
||||
}
|
||||
|
||||
private static void fixRegion(MigrationProfile data, State state, File file) {
|
||||
try {
|
||||
Path path = file.toPath();
|
||||
LOGGER.info("Inspecting " + path);
|
||||
boolean[] changed = new boolean[1];
|
||||
RegionFile region = new RegionFile(path, path.getParent(), true);
|
||||
|
||||
for (int x = 0; x < 32; x++) {
|
||||
for (int z = 0; z < 32; z++) {
|
||||
ChunkPos pos = new ChunkPos(x, z);
|
||||
changed[0] = false;
|
||||
if (region.hasChunk(pos) && !state.didFail) {
|
||||
DataInputStream input = region.getChunkDataInputStream(pos);
|
||||
CompoundTag root = NbtIo.read(input);
|
||||
// if ((root.toString().contains("betternether:chest") || root.toString().contains("bclib:chest"))) {
|
||||
// NbtIo.write(root, new File(file.toString() + "-" + x + "-" + z + ".nbt"));
|
||||
// }
|
||||
input.close();
|
||||
|
||||
//Checking TileEntities
|
||||
ListTag tileEntities = root.getCompound("Level")
|
||||
.getList("TileEntities", Tag.TAG_COMPOUND);
|
||||
fixItemArrayWithID(tileEntities, changed, data, true);
|
||||
|
||||
//Checking Entities
|
||||
ListTag entities = root.getList("Entities", Tag.TAG_COMPOUND);
|
||||
fixItemArrayWithID(entities, changed, data, true);
|
||||
|
||||
//Checking Block Palette
|
||||
ListTag sections = root.getCompound("Level")
|
||||
.getList("Sections", Tag.TAG_COMPOUND);
|
||||
sections.forEach((tag) -> {
|
||||
ListTag palette = ((CompoundTag) tag).getList("Palette", Tag.TAG_COMPOUND);
|
||||
palette.forEach((blockTag) -> {
|
||||
CompoundTag blockTagCompound = ((CompoundTag) blockTag);
|
||||
changed[0] |= data.replaceStringFromIDs(blockTagCompound, "Name");
|
||||
});
|
||||
|
||||
try {
|
||||
changed[0] |= data.patchBlockState(palette,
|
||||
((CompoundTag) tag).getList("BlockStates",
|
||||
Tag.TAG_LONG));
|
||||
} catch (PatchDidiFailException e) {
|
||||
BCLib.LOGGER.error("Failed fixing BlockState in " + pos);
|
||||
state.addError("Failed fixing BlockState in " + pos + " (" + e.getMessage() + ")");
|
||||
state.didFail = true;
|
||||
changed[0] = false;
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
if (changed[0]) {
|
||||
LOGGER.warning("Writing '{}': {}/{}", file, x, z);
|
||||
// NbtIo.write(root, new File(file.toString() + "-" + x + "-" + z + "-changed.nbt"));
|
||||
DataOutputStream output = region.getChunkDataOutputStream(pos);
|
||||
NbtIo.write(root, output);
|
||||
output.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
region.close();
|
||||
} catch (Exception e) {
|
||||
BCLib.LOGGER.error("Failed fixing Region.");
|
||||
state.addError("Failed fixing Region in " + file.getName() + " (" + e.getMessage() + ")");
|
||||
state.didFail = true;
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
static CompoundTag patchConfTag = null;
|
||||
|
||||
static CompoundTag getPatchData() {
|
||||
if (patchConfTag == null) {
|
||||
patchConfTag = WorldDataAPI.getCompoundTag(BCLib.MOD_ID, Configs.MAIN_PATCH_CATEGORY);
|
||||
}
|
||||
return patchConfTag;
|
||||
}
|
||||
|
||||
static void fixItemArrayWithID(ListTag items, boolean[] changed, MigrationProfile data, boolean recursive) {
|
||||
items.forEach(inTag -> {
|
||||
fixID((CompoundTag) inTag, changed, data, recursive);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
static void fixID(CompoundTag inTag, boolean[] changed, MigrationProfile data, boolean recursive) {
|
||||
final CompoundTag tag = inTag;
|
||||
|
||||
changed[0] |= data.replaceStringFromIDs(tag, "id");
|
||||
if (tag.contains("Item")) {
|
||||
CompoundTag item = (CompoundTag) tag.get("Item");
|
||||
fixID(item, changed, data, recursive);
|
||||
}
|
||||
|
||||
if (recursive && tag.contains("Items")) {
|
||||
fixItemArrayWithID(tag.getList("Items", Tag.TAG_COMPOUND), changed, data, true);
|
||||
}
|
||||
if (recursive && tag.contains("Inventory")) {
|
||||
ListTag inventory = tag.getList("Inventory", Tag.TAG_COMPOUND);
|
||||
fixItemArrayWithID(inventory, changed, data, true);
|
||||
}
|
||||
if (tag.contains("tag")) {
|
||||
CompoundTag entityTag = (CompoundTag) tag.get("tag");
|
||||
if (entityTag.contains("BlockEntityTag")) {
|
||||
CompoundTag blockEntityTag = (CompoundTag) entityTag.get("BlockEntityTag");
|
||||
fixID(blockEntityTag, changed, data, recursive);
|
||||
/*ListTag items = blockEntityTag.getList("Items", Tag.TAG_COMPOUND);
|
||||
fixItemArrayWithID(items, changed, data, recursive);*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<File> getAllPlayers(File dir) {
|
||||
List<File> list = new ArrayList<>();
|
||||
dir = new File(dir, "playerdata");
|
||||
if (!dir.exists() || !dir.isDirectory()) {
|
||||
return list;
|
||||
}
|
||||
for (File file : dir.listFiles()) {
|
||||
if (file.isFile() && file.getName().endsWith(".dat")) {
|
||||
list.add(file);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private static List<File> getAllRegions(File dir, List<File> list) {
|
||||
if (list == null) {
|
||||
list = new ArrayList<>();
|
||||
}
|
||||
for (File file : dir.listFiles()) {
|
||||
if (file.isDirectory()) {
|
||||
getAllRegions(file, list);
|
||||
} else if (file.isFile() && file.getName().endsWith(".mca")) {
|
||||
list.add(file);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* register a new Patch
|
||||
*
|
||||
* @param patch A #Supplier that will instantiate the new Patch Object
|
||||
*/
|
||||
public static void registerPatch(Supplier<Patch> patch) {
|
||||
Patch.getALL().add(patch.get());
|
||||
}
|
||||
|
||||
private static CompoundTag readNbt(File file) throws IOException {
|
||||
try {
|
||||
return NbtIo.readCompressed(file);
|
||||
} catch (ZipException | EOFException e) {
|
||||
return NbtIo.read(file);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package org.betterx.bclib.api.datafixer;
|
||||
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
|
||||
import org.betterx.bclib.interfaces.PatchBiFunction;
|
||||
import org.betterx.bclib.interfaces.PatchFunction;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
/**
|
||||
* A Patch for level.dat that is always executed no matter what Patchlevel is set in a world.
|
||||
*/
|
||||
public abstract class ForcedLevelPatch extends Patch {
|
||||
protected ForcedLevelPatch(@NotNull String modID, String version) {
|
||||
super(modID, version, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Map<String, String> getIDReplacements() {
|
||||
return new HashMap<String, String>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final PatchFunction<CompoundTag, Boolean> getWorldDataPatcher() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final PatchBiFunction<ListTag, ListTag, Boolean> getBlockStatePatcher() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final List<String> getWorldDataIDPaths() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PatchFunction<CompoundTag, Boolean> getLevelDatPatcher() {
|
||||
return this::runLevelDatPatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called with the contents of level.dat in {@code root}
|
||||
*
|
||||
* @param root The contents of level.dat
|
||||
* @param profile The active migration profile
|
||||
* @return true, if the run did change the contents of root
|
||||
*/
|
||||
abstract protected Boolean runLevelDatPatch(CompoundTag root, MigrationProfile profile);
|
||||
}
|
||||
|
|
@ -0,0 +1,372 @@
|
|||
package org.betterx.bclib.api.datafixer;
|
||||
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.nbt.NbtIo;
|
||||
import net.minecraft.nbt.Tag;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.api.WorldDataAPI;
|
||||
import org.betterx.bclib.interfaces.PatchBiFunction;
|
||||
import org.betterx.bclib.interfaces.PatchFunction;
|
||||
import org.betterx.bclib.util.ModUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class MigrationProfile {
|
||||
final Set<String> mods;
|
||||
final Map<String, String> idReplacements;
|
||||
final List<PatchFunction<CompoundTag, Boolean>> levelPatchers;
|
||||
final List<PatchBiFunction<ListTag, ListTag, Boolean>> statePatchers;
|
||||
final List<Patch> worldDataPatchers;
|
||||
final Map<String, List<String>> worldDataIDPaths;
|
||||
|
||||
private final CompoundTag config;
|
||||
private CompoundTag level;
|
||||
private File levelBaseDir;
|
||||
private boolean prePatchChangedLevelDat;
|
||||
private boolean didRunPrePatch;
|
||||
private Exception prePatchException;
|
||||
|
||||
MigrationProfile(CompoundTag config, boolean applyAll) {
|
||||
this.config = config;
|
||||
|
||||
this.mods = Collections.unmodifiableSet(Patch.getALL()
|
||||
.stream()
|
||||
.map(p -> p.modID)
|
||||
.collect(Collectors.toSet()));
|
||||
|
||||
HashMap<String, String> replacements = new HashMap<String, String>();
|
||||
List<PatchFunction<CompoundTag, Boolean>> levelPatches = new LinkedList<>();
|
||||
List<Patch> worldDataPatches = new LinkedList<>();
|
||||
List<PatchBiFunction<ListTag, ListTag, Boolean>> statePatches = new LinkedList<>();
|
||||
HashMap<String, List<String>> worldDataIDPaths = new HashMap<>();
|
||||
for (String modID : mods) {
|
||||
|
||||
Patch.getALL()
|
||||
.stream()
|
||||
.filter(p -> p.modID.equals(modID))
|
||||
.forEach(patch -> {
|
||||
List<String> paths = patch.getWorldDataIDPaths();
|
||||
if (paths != null) worldDataIDPaths.put(modID, paths);
|
||||
|
||||
if (applyAll || currentPatchLevel(modID) < patch.level || patch.alwaysApply) {
|
||||
replacements.putAll(patch.getIDReplacements());
|
||||
if (patch.getLevelDatPatcher() != null)
|
||||
levelPatches.add(patch.getLevelDatPatcher());
|
||||
if (patch.getWorldDataPatcher() != null)
|
||||
worldDataPatches.add(patch);
|
||||
if (patch.getBlockStatePatcher() != null)
|
||||
statePatches.add(patch.getBlockStatePatcher());
|
||||
DataFixerAPI.LOGGER.info("Applying " + patch);
|
||||
} else {
|
||||
DataFixerAPI.LOGGER.info("Ignoring " + patch);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.worldDataIDPaths = Collections.unmodifiableMap(worldDataIDPaths);
|
||||
this.idReplacements = Collections.unmodifiableMap(replacements);
|
||||
this.levelPatchers = Collections.unmodifiableList(levelPatches);
|
||||
this.worldDataPatchers = Collections.unmodifiableList(worldDataPatches);
|
||||
this.statePatchers = Collections.unmodifiableList(statePatches);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is supposed to be used by developers to apply id-patches to custom nbt structures. It is only
|
||||
* available in Developer-Mode
|
||||
*/
|
||||
public static void fixCustomFolder(File dir) {
|
||||
if (!BCLib.isDevEnvironment()) return;
|
||||
MigrationProfile profile = Patch.createMigrationData();
|
||||
List<File> nbts = getAllNbts(dir, null);
|
||||
nbts.parallelStream().forEach((file) -> {
|
||||
DataFixerAPI.LOGGER.info("Loading NBT " + file);
|
||||
try {
|
||||
CompoundTag root = NbtIo.readCompressed(file);
|
||||
boolean[] changed = {false};
|
||||
int spawnerIdx = -1;
|
||||
if (root.contains("palette")) {
|
||||
ListTag items = root.getList("palette", Tag.TAG_COMPOUND);
|
||||
for (int idx = 0; idx < items.size(); idx++) {
|
||||
final CompoundTag tag = (CompoundTag) items.get(idx);
|
||||
if (tag.contains("Name") && tag.getString("Name").equals("minecraft:spawner"))
|
||||
spawnerIdx = idx;
|
||||
if (tag.contains("Name") && (tag.getString("Name").equals("minecraft:") || tag.getString("Name")
|
||||
.equals(""))) {
|
||||
System.out.println("Empty Name");
|
||||
}
|
||||
if (tag.contains("id") && (tag.getString("id").equals("minecraft:") || tag.getString("id")
|
||||
.equals(""))) {
|
||||
System.out.println("Empty ID");
|
||||
}
|
||||
changed[0] |= profile.replaceStringFromIDs(tag, "Name");
|
||||
}
|
||||
}
|
||||
|
||||
if (spawnerIdx >= 0 && root.contains("blocks")) {
|
||||
ListTag items = root.getList("blocks", Tag.TAG_COMPOUND);
|
||||
for (int idx = 0; idx < items.size(); idx++) {
|
||||
final CompoundTag blockTag = (CompoundTag) items.get(idx);
|
||||
if (blockTag.contains("state") && blockTag.getInt("state") == spawnerIdx && blockTag.contains(
|
||||
"nbt")) {
|
||||
CompoundTag nbt = blockTag.getCompound("nbt");
|
||||
if (nbt.contains("SpawnData")) {
|
||||
final CompoundTag entity = nbt.getCompound("SpawnData");
|
||||
if (!entity.contains("entity")) {
|
||||
CompoundTag data = new CompoundTag();
|
||||
data.put("entity", entity);
|
||||
nbt.put("SpawnData", data);
|
||||
|
||||
changed[0] = true;
|
||||
}
|
||||
}
|
||||
if (nbt.contains("SpawnPotentials")) {
|
||||
ListTag pots = nbt.getList("SpawnPotentials", Tag.TAG_COMPOUND);
|
||||
for (Tag potItemIn : pots) {
|
||||
final CompoundTag potItem = (CompoundTag) potItemIn;
|
||||
if (potItem.contains("Weight")) {
|
||||
int weight = potItem.getInt("Weight");
|
||||
potItem.putInt("weight", weight);
|
||||
potItem.remove("Weight");
|
||||
|
||||
changed[0] = true;
|
||||
}
|
||||
|
||||
if (potItem.contains("Entity")) {
|
||||
CompoundTag entity = potItem.getCompound("Entity");
|
||||
CompoundTag data = new CompoundTag();
|
||||
data.put("entity", entity);
|
||||
|
||||
potItem.put("data", data);
|
||||
potItem.remove("Entity");
|
||||
|
||||
changed[0] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (changed[0]) {
|
||||
DataFixerAPI.LOGGER.info("Writing NBT " + file);
|
||||
NbtIo.writeCompressed(root, file);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static List<File> getAllNbts(File dir, List<File> list) {
|
||||
if (list == null) {
|
||||
list = new ArrayList<>();
|
||||
}
|
||||
for (File file : dir.listFiles()) {
|
||||
if (file.isDirectory()) {
|
||||
getAllNbts(file, list);
|
||||
} else if (file.isFile() && file.getName().endsWith(".nbt")) {
|
||||
list.add(file);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
final public CompoundTag getLevelDat(File levelBaseDir) {
|
||||
if (level == null || this.levelBaseDir == null || !this.levelBaseDir.equals(levelBaseDir)) {
|
||||
runPrePatches(levelBaseDir);
|
||||
}
|
||||
return level;
|
||||
}
|
||||
|
||||
final public boolean isLevelDatChanged() {
|
||||
return prePatchChangedLevelDat;
|
||||
}
|
||||
|
||||
final public File getLevelDatFile() {
|
||||
return new File(levelBaseDir, "level.dat");
|
||||
}
|
||||
|
||||
final public Exception getPrePatchException() {
|
||||
return prePatchException;
|
||||
}
|
||||
|
||||
|
||||
final public void runPrePatches(File levelBaseDir) {
|
||||
if (didRunPrePatch) {
|
||||
BCLib.LOGGER.warning("Already did run PrePatches for " + this.levelBaseDir + ".");
|
||||
}
|
||||
BCLib.LOGGER.info("Running Pre Patchers on " + levelBaseDir);
|
||||
|
||||
this.levelBaseDir = levelBaseDir;
|
||||
this.level = null;
|
||||
this.prePatchException = null;
|
||||
didRunPrePatch = true;
|
||||
|
||||
this.prePatchChangedLevelDat = runPreLevelPatches(getLevelDatFile());
|
||||
}
|
||||
|
||||
private boolean runPreLevelPatches(File levelDat) {
|
||||
try {
|
||||
level = NbtIo.readCompressed(levelDat);
|
||||
|
||||
boolean changed = patchLevelDat(level);
|
||||
return changed;
|
||||
} catch (IOException | PatchDidiFailException e) {
|
||||
prePatchException = e;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final public void markApplied() {
|
||||
for (String modID : mods) {
|
||||
DataFixerAPI.LOGGER.info("Updating Patch-Level for '{}' from {} to {}",
|
||||
modID,
|
||||
ModUtil.convertModVersion(currentPatchLevel(modID)),
|
||||
ModUtil.convertModVersion(Patch.maxPatchLevel(modID)));
|
||||
if (config != null)
|
||||
config.putString(modID, Patch.maxPatchVersion(modID));
|
||||
}
|
||||
}
|
||||
|
||||
public String currentPatchVersion(@NotNull String modID) {
|
||||
if (config == null || !config.contains(modID)) return "0.0.0";
|
||||
return config.getString(modID);
|
||||
}
|
||||
|
||||
public int currentPatchLevel(@NotNull String modID) {
|
||||
return ModUtil.convertModVersion(currentPatchVersion(modID));
|
||||
}
|
||||
|
||||
public boolean hasAnyFixes() {
|
||||
boolean hasLevelDatPatches;
|
||||
if (didRunPrePatch != false) {
|
||||
hasLevelDatPatches = prePatchChangedLevelDat;
|
||||
} else {
|
||||
hasLevelDatPatches = levelPatchers.size() > 0;
|
||||
}
|
||||
|
||||
return idReplacements.size() > 0 || hasLevelDatPatches || worldDataPatchers.size() > 0;
|
||||
}
|
||||
|
||||
public String replaceStringFromIDs(@NotNull String val) {
|
||||
final String replace = idReplacements.get(val);
|
||||
return replace;
|
||||
}
|
||||
|
||||
public boolean replaceStringFromIDs(@NotNull CompoundTag tag, @NotNull String key) {
|
||||
if (!tag.contains(key)) return false;
|
||||
|
||||
final String val = tag.getString(key);
|
||||
final String replace = idReplacements.get(val);
|
||||
|
||||
if (replace != null) {
|
||||
DataFixerAPI.LOGGER.warning("Replacing ID '{}' with '{}'.", val, replace);
|
||||
tag.putString(key, replace);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean replaceIDatPath(@NotNull ListTag list, @NotNull String[] parts, int level) {
|
||||
boolean[] changed = {false};
|
||||
if (level == parts.length - 1) {
|
||||
DataFixerAPI.fixItemArrayWithID(list, changed, this, true);
|
||||
} else {
|
||||
list.forEach(inTag -> changed[0] |= replaceIDatPath((CompoundTag) inTag, parts, level + 1));
|
||||
}
|
||||
return changed[0];
|
||||
}
|
||||
|
||||
private boolean replaceIDatPath(@NotNull CompoundTag tag, @NotNull String[] parts, int level) {
|
||||
boolean changed = false;
|
||||
for (int i = level; i < parts.length - 1; i++) {
|
||||
final String part = parts[i];
|
||||
if (tag.contains(part)) {
|
||||
final byte type = tag.getTagType(part);
|
||||
if (type == Tag.TAG_LIST) {
|
||||
ListTag list = tag.getList(part, Tag.TAG_COMPOUND);
|
||||
return replaceIDatPath(list, parts, i);
|
||||
} else if (type == Tag.TAG_COMPOUND) {
|
||||
tag = tag.getCompound(part);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (tag != null && parts.length > 0) {
|
||||
final String key = parts[parts.length - 1];
|
||||
final byte type = tag.getTagType(key);
|
||||
if (type == Tag.TAG_LIST) {
|
||||
final ListTag list = tag.getList(key, Tag.TAG_COMPOUND);
|
||||
final boolean[] _changed = {false};
|
||||
if (list.size() == 0) {
|
||||
_changed[0] = DataFixerAPI.fixStringIDList(tag, key, this);
|
||||
} else {
|
||||
DataFixerAPI.fixItemArrayWithID(list, _changed, this, true);
|
||||
}
|
||||
return _changed[0];
|
||||
} else if (type == Tag.TAG_STRING) {
|
||||
return replaceStringFromIDs(tag, key);
|
||||
} else if (type == Tag.TAG_COMPOUND) {
|
||||
final CompoundTag cTag = tag.getCompound(key);
|
||||
boolean[] _changed = {false};
|
||||
DataFixerAPI.fixID(cTag, _changed, this, true);
|
||||
return _changed[0];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean replaceIDatPath(@NotNull CompoundTag root, @NotNull String path) {
|
||||
String[] parts = path.split("\\.");
|
||||
return replaceIDatPath(root, parts, 0);
|
||||
}
|
||||
|
||||
public boolean patchLevelDat(@NotNull CompoundTag level) throws PatchDidiFailException {
|
||||
boolean changed = false;
|
||||
for (PatchFunction<CompoundTag, Boolean> f : levelPatchers) {
|
||||
changed |= f.apply(level, this);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
public void patchWorldData() throws PatchDidiFailException {
|
||||
for (Patch patch : worldDataPatchers) {
|
||||
CompoundTag root = WorldDataAPI.getRootTag(patch.modID);
|
||||
boolean changed = patch.getWorldDataPatcher().apply(root, this);
|
||||
if (changed) {
|
||||
WorldDataAPI.saveFile(patch.modID);
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<String, List<String>> entry : worldDataIDPaths.entrySet()) {
|
||||
CompoundTag root = WorldDataAPI.getRootTag(entry.getKey());
|
||||
boolean[] changed = {false};
|
||||
entry.getValue().forEach(path -> {
|
||||
changed[0] |= replaceIDatPath(root, path);
|
||||
});
|
||||
|
||||
if (changed[0]) {
|
||||
WorldDataAPI.saveFile(entry.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean patchBlockState(ListTag palette, ListTag states) throws PatchDidiFailException {
|
||||
boolean changed = false;
|
||||
for (PatchBiFunction<ListTag, ListTag, Boolean> f : statePatchers) {
|
||||
changed |= f.apply(palette, states, this);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
}
|
238
src/main/java/org/betterx/bclib/api/datafixer/Patch.java
Normal file
238
src/main/java/org/betterx/bclib/api/datafixer/Patch.java
Normal file
|
@ -0,0 +1,238 @@
|
|||
package org.betterx.bclib.api.datafixer;
|
||||
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
|
||||
import org.betterx.bclib.api.WorldDataAPI;
|
||||
import org.betterx.bclib.interfaces.PatchBiFunction;
|
||||
import org.betterx.bclib.interfaces.PatchFunction;
|
||||
import org.betterx.bclib.util.ModUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public abstract class Patch {
|
||||
private static final List<Patch> ALL = new ArrayList<>(10);
|
||||
|
||||
/**
|
||||
* The Patch-Level derived from {@link #version}
|
||||
*/
|
||||
public final int level;
|
||||
|
||||
/**
|
||||
* The Patch-Version string
|
||||
*/
|
||||
public final String version;
|
||||
|
||||
/**
|
||||
* The Mod-ID that registered this Patch
|
||||
*/
|
||||
|
||||
@NotNull
|
||||
public final String modID;
|
||||
|
||||
/**
|
||||
* This Mod is tested for each level start
|
||||
*/
|
||||
public final boolean alwaysApply;
|
||||
|
||||
static List<Patch> getALL() {
|
||||
return ALL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the highest Patch-Version that is available for the given mod. If no patches were
|
||||
* registerd for the mod, this will return 0.0.0
|
||||
*
|
||||
* @param modID The ID of the mod you want to query
|
||||
* @return The highest Patch-Version that was found
|
||||
*/
|
||||
public static String maxPatchVersion(@NotNull String modID) {
|
||||
return ALL.stream().filter(p -> p.modID.equals(modID)).map(p -> p.version).reduce((p, c) -> c).orElse("0.0.0");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the highest patch-level that is available for the given mod. If no patches were
|
||||
* registerd for the mod, this will return 0
|
||||
*
|
||||
* @param modID The ID of the mod you want to query
|
||||
* @return The highest Patch-Level that was found
|
||||
*/
|
||||
public static int maxPatchLevel(@NotNull String modID) {
|
||||
return ALL.stream().filter(p -> p.modID.equals(modID)).mapToInt(p -> p.level).max().orElse(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by inheriting classes.
|
||||
* <p>
|
||||
* Performs some sanity checks on the values and might throw a #RuntimeException if any
|
||||
* inconsistencies are found.
|
||||
*
|
||||
* @param modID The ID of the Mod you want to register a patch for. This should be your
|
||||
* ModID only. The ModID can not be {@code null} or an empty String.
|
||||
* @param version The mod-version that introduces the patch. This needs Semantic-Version String
|
||||
* like x.x.x. Developers are responsible for registering their patches in the correct
|
||||
* order (with increasing versions). You are not allowed to register a new
|
||||
* Patch with a version lower or equal than
|
||||
* {@link Patch#maxPatchVersion(String)}
|
||||
*/
|
||||
protected Patch(@NotNull String modID, String version) {
|
||||
this(modID, version, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal Constructor used to create patches that can allways run (no matter what patchlevel a level has)
|
||||
*
|
||||
* @param modID The ID of the Mod
|
||||
* @param version The mod-version that introduces the patch. When {@Code runAllways} is set, this version will
|
||||
* determine the patchlevel that is written to the level
|
||||
* @param alwaysApply When true, this patch is always active, no matter the patchlevel of the world.
|
||||
* This should be used sparingly and just for patches that apply to level.dat (as they only take
|
||||
* effect when changes are detected). Use {@link ForcedLevelPatch} to instatiate.
|
||||
*/
|
||||
Patch(@NotNull String modID, String version, boolean alwaysApply) {
|
||||
//Patchlevels need to be unique and registered in ascending order
|
||||
if (modID == null || modID.isEmpty()) {
|
||||
throw new RuntimeException("[INTERNAL ERROR] Patches need a valid modID!");
|
||||
}
|
||||
|
||||
if (version == null || version.isEmpty()) {
|
||||
throw new RuntimeException("Invalid Mod-Version");
|
||||
}
|
||||
|
||||
this.version = version;
|
||||
this.alwaysApply = alwaysApply;
|
||||
this.level = ModUtil.convertModVersion(version);
|
||||
if (!ALL.stream().filter(p -> p.modID.equals(modID)).noneMatch(p -> p.level >= this.level) || this.level <= 0) {
|
||||
throw new RuntimeException(
|
||||
"[INTERNAL ERROR] Patch-levels need to be created in ascending order beginning with 1.");
|
||||
}
|
||||
|
||||
this.modID = modID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Patch{" + modID + ':' + version + ':' + level + '}';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return block data fixes. Fixes will be applied on world load if current patch-level for
|
||||
* the linked mod is lower than the {@link #level}.
|
||||
* <p>
|
||||
* The default implementation of this method returns an empty map.
|
||||
*
|
||||
* @return The returned Map should contain the replacements. All occurences of the
|
||||
* {@code KeySet} are replaced with the associated value.
|
||||
*/
|
||||
public Map<String, String> getIDReplacements() {
|
||||
return new HashMap<String, String>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link PatchFunction} that is called with the content of <i>level.dat</i>.
|
||||
* <p>
|
||||
* The function needs to return {@code true}, if changes were made to the data.
|
||||
* If an error occurs, the method should throw a {@link PatchDidiFailException}
|
||||
* <p>
|
||||
* The default implementation of this method returns null.
|
||||
*
|
||||
* @return {@code true} if changes were applied and we need to save the data
|
||||
*/
|
||||
public PatchFunction<CompoundTag, Boolean> getLevelDatPatcher() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link PatchFunction} that is called with the content from the
|
||||
* {@link WorldDataAPI} for this Mod.
|
||||
* The function needs to return {@code true}, if changes were made to the data.
|
||||
* If an error occurs, the method should throw a {@link PatchDidiFailException}
|
||||
* <p>
|
||||
* The default implementation of this method returns null.
|
||||
*
|
||||
* @return {@code true} if changes were applied and we need to save the data
|
||||
*/
|
||||
public PatchFunction<CompoundTag, Boolean> getWorldDataPatcher() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link PatchBiFunction} that is called with pallette and blockstate of
|
||||
* each chunk in every region. This method is called AFTER all ID replacements
|
||||
* from {@link #getIDReplacements()} were applied to the pallete.
|
||||
* <p>
|
||||
* The first parameter is the palette and the second is the blockstate.
|
||||
* <p>
|
||||
* The function needs to return {@code true}, if changes were made to the data.
|
||||
* If an error occurs, the method should throw a {@link PatchDidiFailException}
|
||||
* <p>
|
||||
* The default implementation of this method returns null.
|
||||
*
|
||||
* @return {@code true} if changes were applied and we need to save the data
|
||||
*/
|
||||
public PatchBiFunction<ListTag, ListTag, Boolean> getBlockStatePatcher() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates ready to use data for all currently registered patches. The list of
|
||||
* patches is selected by the current patch-level of the world.
|
||||
* <p>
|
||||
* A {@link #Patch} with a given {@link #level} is only included if the patch-level of the
|
||||
* world is less
|
||||
*
|
||||
* @param config The current patch-level configuration*
|
||||
* @return a new {@link MigrationProfile} Object.
|
||||
*/
|
||||
static MigrationProfile createMigrationData(CompoundTag config) {
|
||||
return new MigrationProfile(config, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is supposed to be used by developers to apply id-patches to custom nbt structures. It is only
|
||||
* available in Developer-Mode
|
||||
*/
|
||||
static MigrationProfile createMigrationData() {
|
||||
return new MigrationProfile(null, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of paths where your mod stores IDs in your {@link WorldDataAPI}-File.
|
||||
* <p>
|
||||
* {@link DataFixerAPI} will use information from the latest patch that returns a non-null-result. This list is used
|
||||
* to automatically fix changed IDs from all active patches (see {@link Patch#getIDReplacements()}
|
||||
* <p>
|
||||
* The end of the path can either be a {@link net.minecraft.nbt.StringTag}, a {@link net.minecraft.nbt.ListTag} or
|
||||
* a {@link CompoundTag}. If the Path contains a non-leaf {@link net.minecraft.nbt.ListTag}, all members of that
|
||||
* list will be processed. For example:
|
||||
* <pre>
|
||||
* - global +
|
||||
* | - key (String)
|
||||
* | - items (List) +
|
||||
* | - { id (String) }
|
||||
* | - { id (String) }
|
||||
* </pre>
|
||||
* The path <b>global.items.id</b> will fix all <i>id</i>-entries in the <i>items</i>-list, while the path
|
||||
* <b>global.key</b> will only fix the <i>key</i>-entry.
|
||||
* <p>
|
||||
* if the leaf-entry (= the last part of the path, which would be <i>items</i> in <b>global.items</b>) is a
|
||||
* {@link CompoundTag}, the system will fix any <i>id</i> entry. If the {@link CompoundTag} contains an <i>item</i>
|
||||
* or <i>tag.BlockEntityTag</i> entry, the system will recursivley continue with those. If an <i>items</i>
|
||||
* or <i>inventory</i>-{@link net.minecraft.nbt.ListTag} was found, the system will continue recursivley with
|
||||
* every item of that list.
|
||||
* <p>
|
||||
* if the leaf-entry is a {@link net.minecraft.nbt.ListTag}, it is handle the same as a child <i>items</i> entry
|
||||
* of a {@link CompoundTag}.
|
||||
*
|
||||
* @return {@code null} if nothing changes or a list of Paths in your {@link WorldDataAPI}-File.
|
||||
* Paths are dot-seperated (see {@link WorldDataAPI#getCompoundTag(String, String)}).
|
||||
*/
|
||||
public List<String> getWorldDataIDPaths() {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package org.betterx.bclib.api.datafixer;
|
||||
|
||||
public class PatchDidiFailException extends Exception {
|
||||
public PatchDidiFailException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public PatchDidiFailException(Exception e) {
|
||||
super(e);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
package org.betterx.bclib.api.features;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.levelgen.GenerationStep.Decoration;
|
||||
import net.minecraft.world.level.levelgen.feature.Feature;
|
||||
import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration;
|
||||
import net.minecraft.world.level.levelgen.feature.configurations.OreConfiguration;
|
||||
import net.minecraft.world.level.levelgen.placement.PlacementModifier;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.BlockMatchTest;
|
||||
|
||||
import org.betterx.bclib.world.features.BCLFeature;
|
||||
|
||||
public class BCLCommonFeatures {
|
||||
/**
|
||||
* Will create a basic plant feature.
|
||||
*
|
||||
* @param id {@link ResourceLocation} feature ID.
|
||||
* @param feature {@link Feature} with {@link NoneFeatureConfiguration} config.
|
||||
* @param density iterations per chunk.
|
||||
* @return new BCLFeature instance.
|
||||
*/
|
||||
public static BCLFeature makeVegetationFeature(ResourceLocation id,
|
||||
Feature<NoneFeatureConfiguration> feature,
|
||||
int density) {
|
||||
return makeVegetationFeature(id, feature, density, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will create a basic plant feature.
|
||||
*
|
||||
* @param id {@link ResourceLocation} feature ID.
|
||||
* @param feature {@link Feature} with {@link NoneFeatureConfiguration} config.
|
||||
* @param density iterations per chunk.
|
||||
* @param allHeight if {@code true} will generate plant on all layers, if {@code false} - only on surface.
|
||||
* @return new BCLFeature instance.
|
||||
*/
|
||||
public static BCLFeature makeVegetationFeature(ResourceLocation id,
|
||||
Feature<NoneFeatureConfiguration> feature,
|
||||
int density,
|
||||
boolean allHeight) {
|
||||
if (allHeight) {
|
||||
return BCLFeatureBuilder.start(id, feature).countLayers(density).squarePlacement().onlyInBiome().build();
|
||||
} else {
|
||||
return BCLFeatureBuilder
|
||||
.start(id, feature)
|
||||
.countMax(density)
|
||||
.squarePlacement()
|
||||
.heightmap()
|
||||
.onlyInBiome()
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Will create feature which will be generated once in each chunk.
|
||||
*
|
||||
* @param id {@link ResourceLocation} feature ID.
|
||||
* @param decoration {@link Decoration} feature step.
|
||||
* @param feature {@link Feature} with {@link NoneFeatureConfiguration} config.
|
||||
* @return new BCLFeature instance.
|
||||
*/
|
||||
public static BCLFeature makeChunkFeature(ResourceLocation id,
|
||||
Decoration decoration,
|
||||
Feature<NoneFeatureConfiguration> feature) {
|
||||
return BCLFeatureBuilder.start(id, feature).decoration(decoration).count(1).onlyInBiome().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Will create feature with chanced decoration, chance for feature to generate per chunk is 1 / chance.
|
||||
*
|
||||
* @param id {@link ResourceLocation} feature ID.
|
||||
* @param decoration {@link Decoration} feature step.
|
||||
* @param feature {@link Feature} with {@link NoneFeatureConfiguration} config.
|
||||
* @param chance chance for feature to be generated in.
|
||||
* @return new BCLFeature instance.
|
||||
*/
|
||||
public static BCLFeature makeChancedFeature(ResourceLocation id,
|
||||
Decoration decoration,
|
||||
Feature<NoneFeatureConfiguration> feature,
|
||||
int chance) {
|
||||
return BCLFeatureBuilder.start(id, feature)
|
||||
.decoration(decoration)
|
||||
.oncePerChunks(chance)
|
||||
.squarePlacement()
|
||||
.onlyInBiome()
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Will create feature with specified generation iterations per chunk.
|
||||
*
|
||||
* @param id {@link ResourceLocation} feature ID.
|
||||
* @param decoration {@link Decoration} feature step.
|
||||
* @param feature {@link Feature} with {@link NoneFeatureConfiguration} config.
|
||||
* @param count iterations steps.
|
||||
* @return new BCLFeature instance.
|
||||
*/
|
||||
public static BCLFeature makeCountFeature(ResourceLocation id,
|
||||
Decoration decoration,
|
||||
Feature<NoneFeatureConfiguration> feature,
|
||||
int count) {
|
||||
return BCLFeatureBuilder.start(id, feature)
|
||||
.decoration(decoration)
|
||||
.count(count)
|
||||
.squarePlacement()
|
||||
.onlyInBiome()
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Will create a basic ore feature.
|
||||
*
|
||||
* @param id {@link ResourceLocation} feature ID.
|
||||
* @param blockOre {@link Decoration} feature step.
|
||||
* @param hostBlock {@link Block} to generate feature in.
|
||||
* @param veins iterations per chunk.
|
||||
* @param veinSize size of ore vein.
|
||||
* @param airDiscardChance chance that this orge gets discarded when it is exposed to air
|
||||
* @param placement {@link net.minecraft.world.level.levelgen.placement.PlacementModifier} for the ore distribution,
|
||||
* for example {@code PlacementUtils.FULL_RANGE}, {@code PlacementUtils.RANGE_10_10}
|
||||
* @param rare when true, this is placed as a rare resource
|
||||
* @return new BCLFeature instance.
|
||||
*/
|
||||
public static BCLFeature makeOreFeature(ResourceLocation id,
|
||||
Block blockOre,
|
||||
Block hostBlock,
|
||||
int veins,
|
||||
int veinSize,
|
||||
float airDiscardChance,
|
||||
PlacementModifier placement,
|
||||
boolean rare) {
|
||||
BCLFeatureBuilder builder = BCLFeatureBuilder.start(id, Feature.ORE).decoration(Decoration.UNDERGROUND_ORES);
|
||||
|
||||
if (rare) {
|
||||
builder.oncePerChunks(veins);
|
||||
} else {
|
||||
builder.count(veins);
|
||||
}
|
||||
|
||||
builder.modifier(placement).squarePlacement().onlyInBiome();
|
||||
|
||||
return builder.build(new OreConfiguration(
|
||||
new BlockMatchTest(hostBlock),
|
||||
blockOre.defaultBlockState(),
|
||||
veinSize,
|
||||
airDiscardChance
|
||||
));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,162 @@
|
|||
package org.betterx.bclib.api.features;
|
||||
|
||||
import net.minecraft.data.worldgen.placement.PlacementUtils;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.valueproviders.UniformInt;
|
||||
import net.minecraft.world.level.levelgen.GenerationStep.Decoration;
|
||||
import net.minecraft.world.level.levelgen.feature.Feature;
|
||||
import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration;
|
||||
import net.minecraft.world.level.levelgen.placement.*;
|
||||
|
||||
import org.betterx.bclib.world.features.BCLFeature;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class BCLFeatureBuilder<FC extends FeatureConfiguration, F extends Feature<FC>> {
|
||||
private static final BCLFeatureBuilder INSTANCE = new BCLFeatureBuilder();
|
||||
private final List<PlacementModifier> modifications = new ArrayList<>(16);
|
||||
private ResourceLocation featureID;
|
||||
private Decoration decoration;
|
||||
private F feature;
|
||||
|
||||
private BCLFeatureBuilder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a new {@link BCLFeature} builder.
|
||||
*
|
||||
* @param featureID {@link ResourceLocation} feature identifier.
|
||||
* @param feature {@link Feature} to construct.
|
||||
* @return {@link BCLFeatureBuilder} instance.
|
||||
*/
|
||||
public static BCLFeatureBuilder start(ResourceLocation featureID, Feature<?> feature) {
|
||||
INSTANCE.decoration = Decoration.VEGETAL_DECORATION;
|
||||
INSTANCE.modifications.clear();
|
||||
INSTANCE.featureID = featureID;
|
||||
INSTANCE.feature = feature;
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set generation step for the feature. Default is {@code VEGETAL_DECORATION}.
|
||||
*
|
||||
* @param decoration {@link Decoration} step.
|
||||
* @return same {@link BCLFeatureBuilder} instance.
|
||||
*/
|
||||
public BCLFeatureBuilder decoration(Decoration decoration) {
|
||||
this.decoration = decoration;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add feature placement modifier. Used as a condition for feature how to generate.
|
||||
*
|
||||
* @param modifier {@link PlacementModifier}.
|
||||
* @return same {@link BCLFeatureBuilder} instance.
|
||||
*/
|
||||
public BCLFeatureBuilder modifier(PlacementModifier modifier) {
|
||||
modifications.add(modifier);
|
||||
return this;
|
||||
}
|
||||
|
||||
public BCLFeatureBuilder modifier(List<PlacementModifier> modifiers) {
|
||||
modifications.addAll(modifiers);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate feature in certain iterations (per chunk).
|
||||
*
|
||||
* @param count how many times feature will be generated in chunk.
|
||||
* @return same {@link BCLFeatureBuilder} instance.
|
||||
*/
|
||||
public BCLFeatureBuilder count(int count) {
|
||||
return modifier(CountPlacement.of(count));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate feature in certain iterations (per chunk). Count can be between 0 and max value.
|
||||
*
|
||||
* @param count maximum amount of iterations per chunk.
|
||||
* @return same {@link BCLFeatureBuilder} instance.
|
||||
*/
|
||||
public BCLFeatureBuilder countMax(int count) {
|
||||
return modifier(CountPlacement.of(UniformInt.of(0, count)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate feature in certain iterations (per chunk).
|
||||
* Feature will be generated on all layers (example - Nether plants).
|
||||
*
|
||||
* @param count how many times feature will be generated in chunk layers.
|
||||
* @return same {@link BCLFeatureBuilder} instance.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public BCLFeatureBuilder countLayers(int count) {
|
||||
return modifier(CountOnEveryLayerPlacement.of(count));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate feature in certain iterations (per chunk). Count can be between 0 and max value.
|
||||
* Feature will be generated on all layers (example - Nether plants).
|
||||
*
|
||||
* @param count maximum amount of iterations per chunk layers.
|
||||
* @return same {@link BCLFeatureBuilder} instance.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public BCLFeatureBuilder countLayersMax(int count) {
|
||||
return modifier(CountOnEveryLayerPlacement.of(UniformInt.of(0, count)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Will place feature once in certain amount of chunks (in average).
|
||||
*
|
||||
* @param chunks amount of chunks.
|
||||
* @return same {@link BCLFeatureBuilder} instance.
|
||||
*/
|
||||
public BCLFeatureBuilder oncePerChunks(int chunks) {
|
||||
return modifier(RarityFilter.onAverageOnceEvery(chunks));
|
||||
}
|
||||
|
||||
/**
|
||||
* Restricts feature generation only to biome where feature was added.
|
||||
*
|
||||
* @return same {@link BCLFeatureBuilder} instance.
|
||||
*/
|
||||
public BCLFeatureBuilder onlyInBiome() {
|
||||
return modifier(BiomeFilter.biome());
|
||||
}
|
||||
|
||||
// Are these two things required in 1.18.1?
|
||||
// TODO - add information
|
||||
public BCLFeatureBuilder squarePlacement() {
|
||||
return modifier(InSquarePlacement.spread());
|
||||
}
|
||||
|
||||
// TODO - add information
|
||||
public BCLFeatureBuilder heightmap() {
|
||||
return modifier(PlacementUtils.HEIGHTMAP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new {@link BCLFeature} instance. Features will be registered during this process.
|
||||
*
|
||||
* @param configuration any {@link FeatureConfiguration} for provided {@link Feature}.
|
||||
* @return created {@link BCLFeature} instance.
|
||||
*/
|
||||
public BCLFeature build(FC configuration) {
|
||||
PlacementModifier[] modifiers = modifications.toArray(new PlacementModifier[modifications.size()]);
|
||||
return new BCLFeature(featureID, feature, decoration, configuration, modifiers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new {@link BCLFeature} instance with {@code NONE} {@link FeatureConfiguration}.
|
||||
* Features will be registered during this process.
|
||||
*
|
||||
* @return created {@link BCLFeature} instance.
|
||||
*/
|
||||
public BCLFeature build() {
|
||||
return build((FC) FeatureConfiguration.NONE);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,361 @@
|
|||
package org.betterx.bclib.api.spawning;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.Difficulty;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.Mob;
|
||||
import net.minecraft.world.entity.SpawnPlacements.SpawnPredicate;
|
||||
import net.minecraft.world.entity.SpawnPlacements.Type;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.levelgen.Heightmap.Types;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
|
||||
import net.fabricmc.fabric.mixin.object.builder.SpawnRestrictionAccessor;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import org.betterx.bclib.entity.BCLEntityWrapper;
|
||||
import org.betterx.bclib.interfaces.SpawnRule;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class SpawnRuleBuilder<M extends Mob> {
|
||||
private static final Map<String, SpawnRuleEntry> RULES_CACHE = Maps.newHashMap();
|
||||
private static final SpawnRuleBuilder INSTANCE = new SpawnRuleBuilder();
|
||||
private final List<SpawnRuleEntry> rules = Lists.newArrayList();
|
||||
private SpawnRuleEntry entryInstance;
|
||||
private EntityType<M> entityType;
|
||||
|
||||
private SpawnRuleBuilder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts new rule building process.
|
||||
*
|
||||
* @param entityType The entity you want to build a rule for
|
||||
* @return prepared {@link SpawnRuleBuilder} instance.
|
||||
*/
|
||||
public static SpawnRuleBuilder start(EntityType<? extends Mob> entityType) {
|
||||
INSTANCE.entityType = entityType;
|
||||
INSTANCE.rules.clear();
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts new rule building process.
|
||||
*
|
||||
* @param wrapper The entity you want to build a rule for
|
||||
* @return prepared {@link SpawnRuleBuilder} instance.
|
||||
*/
|
||||
public static SpawnRuleBuilder start(BCLEntityWrapper<? extends Mob> wrapper) {
|
||||
SpawnRuleBuilder builder = start(wrapper.type());
|
||||
if (!wrapper.canSpawn()) {
|
||||
builder.preventSpawn();
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop entity spawn entierly
|
||||
*
|
||||
* @return same {@link SpawnRuleBuilder} instance.
|
||||
*/
|
||||
public SpawnRuleBuilder preventSpawn() {
|
||||
entryInstance = getFromCache("prevent", () -> {
|
||||
return new SpawnRuleEntry(-1, (type, world, spawnReason, pos, random) -> false);
|
||||
});
|
||||
rules.add(entryInstance);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop entity spawn on peaceful {@link Difficulty}
|
||||
*
|
||||
* @return same {@link SpawnRuleBuilder} instance.
|
||||
*/
|
||||
public SpawnRuleBuilder notPeaceful() {
|
||||
entryInstance = getFromCache("not_peaceful", () -> {
|
||||
return new SpawnRuleEntry(0,
|
||||
(type, world, spawnReason, pos, random) -> world.getDifficulty() != Difficulty.PEACEFUL);
|
||||
});
|
||||
rules.add(entryInstance);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restricts entity spawn above world surface (flying mobs).
|
||||
*
|
||||
* @param minHeight minimal spawn height.
|
||||
* @return same {@link SpawnRuleBuilder} instance.
|
||||
*/
|
||||
public SpawnRuleBuilder aboveGround(int minHeight) {
|
||||
entryInstance = getFromCache("above_ground", () -> {
|
||||
return new SpawnRuleEntry(0, (type, world, spawnReason, pos, random) -> {
|
||||
if (pos.getY() < world.getMinBuildHeight() + 2) {
|
||||
return false;
|
||||
}
|
||||
return pos.getY() > world.getHeight(Types.WORLD_SURFACE, pos.getX(), pos.getZ()) + minHeight;
|
||||
});
|
||||
});
|
||||
rules.add(entryInstance);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restricts entity spawn below world logical height (useful for Nether mobs).
|
||||
*
|
||||
* @return same {@link SpawnRuleBuilder} instance.
|
||||
*/
|
||||
public SpawnRuleBuilder belowMaxHeight() {
|
||||
entryInstance = getFromCache("below_max_height", () -> {
|
||||
return new SpawnRuleEntry(0,
|
||||
(type, world, spawnReason, pos, random) -> pos.getY() < world.dimensionType()
|
||||
.logicalHeight());
|
||||
});
|
||||
rules.add(entryInstance);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restricts spawning only to vanilla valid blocks.
|
||||
*
|
||||
* @return same {@link SpawnRuleBuilder} instance.
|
||||
*/
|
||||
public SpawnRuleBuilder onlyOnValidBlocks() {
|
||||
entryInstance = getFromCache("only_on_valid_blocks", () -> {
|
||||
return new SpawnRuleEntry(0, (type, world, spawnReason, pos, random) -> {
|
||||
BlockPos below = pos.below();
|
||||
return world.getBlockState(below).isValidSpawn(world, below, type);
|
||||
});
|
||||
});
|
||||
rules.add(entryInstance);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restricts spawning only to specified blocks.
|
||||
*
|
||||
* @return same {@link SpawnRuleBuilder} instance.
|
||||
*/
|
||||
public SpawnRuleBuilder onlyOnBlocks(Block... blocks) {
|
||||
final Block[] floorBlocks = blocks;
|
||||
Arrays.sort(floorBlocks, Comparator.comparing(Block::getDescriptionId));
|
||||
|
||||
StringBuilder builder = new StringBuilder("only_on_blocks");
|
||||
for (Block block : floorBlocks) {
|
||||
builder.append('_');
|
||||
builder.append(block.getDescriptionId());
|
||||
}
|
||||
|
||||
entryInstance = getFromCache(builder.toString(), () -> {
|
||||
return new SpawnRuleEntry(0, (type, world, spawnReason, pos, random) -> {
|
||||
Block below = world.getBlockState(pos.below()).getBlock();
|
||||
for (Block floor : floorBlocks) {
|
||||
if (floor == below) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
rules.add(entryInstance);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will spawn entity with 1 / chance probability (randomly).
|
||||
*
|
||||
* @param chance probability limit.
|
||||
* @return same {@link SpawnRuleBuilder} instance.
|
||||
*/
|
||||
public SpawnRuleBuilder withChance(int chance) {
|
||||
entryInstance = getFromCache("with_chance_" + chance, () -> {
|
||||
return new SpawnRuleEntry(1, (type, world, spawnReason, pos, random) -> random.nextInt(chance) == 0);
|
||||
});
|
||||
rules.add(entryInstance);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will spawn entity only below specified brightness value.
|
||||
*
|
||||
* @param lightLevel light level upper limit.
|
||||
* @return same {@link SpawnRuleBuilder} instance.
|
||||
*/
|
||||
public SpawnRuleBuilder belowBrightness(int lightLevel) {
|
||||
entryInstance = getFromCache("below_brightness_" + lightLevel, () -> {
|
||||
return new SpawnRuleEntry(2,
|
||||
(type, world, spawnReason, pos, random) -> world.getMaxLocalRawBrightness(pos) <= lightLevel);
|
||||
});
|
||||
rules.add(entryInstance);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will spawn entity only above specified brightness value.
|
||||
*
|
||||
* @param lightLevel light level lower limit.
|
||||
* @return same {@link SpawnRuleBuilder} instance.
|
||||
*/
|
||||
public SpawnRuleBuilder aboveBrightness(int lightLevel) {
|
||||
entryInstance = getFromCache("above_brightness_" + lightLevel, () -> {
|
||||
return new SpawnRuleEntry(2,
|
||||
(type, world, spawnReason, pos, random) -> world.getMaxLocalRawBrightness(pos) >= lightLevel);
|
||||
});
|
||||
rules.add(entryInstance);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entity spawn will follow common vanilla spawn rules - spawn in darkness and not on peaceful level.
|
||||
*
|
||||
* @param lightLevel light level upper limit.
|
||||
* @return same {@link SpawnRuleBuilder} instance.
|
||||
*/
|
||||
public SpawnRuleBuilder hostile(int lightLevel) {
|
||||
return notPeaceful().belowBrightness(lightLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Entity spawn will follow common vanilla spawn rules - spawn in darkness (below light level 7) and not on peaceful level.
|
||||
*
|
||||
* @return same {@link SpawnRuleBuilder} instance.
|
||||
*/
|
||||
public SpawnRuleBuilder vanillaHostile() {
|
||||
return hostile(7);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will spawn entity only if count of nearby entities will be lower than specified.
|
||||
*
|
||||
* @param selectorType selector {@link EntityType} to search.
|
||||
* @param count max entity count.
|
||||
* @param side side of box to search in.
|
||||
* @return same {@link SpawnRuleBuilder} instance.
|
||||
*/
|
||||
public SpawnRuleBuilder maxNearby(EntityType<?> selectorType, int count, int side) {
|
||||
final Class<? extends Entity> baseClass = selectorType.getBaseClass();
|
||||
entryInstance = getFromCache("max_nearby_" + selectorType.getDescriptionId() + "_" + count + "_" + side, () -> {
|
||||
return new SpawnRuleEntry(3, (type, world, spawnReason, pos, random) -> {
|
||||
try {
|
||||
final AABB box = new AABB(pos).inflate(side, world.getHeight(), side);
|
||||
final List<?> list = world.getEntitiesOfClass(baseClass, box, (entity) -> true);
|
||||
return list.size() < count;
|
||||
} catch (Exception e) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
});
|
||||
rules.add(entryInstance);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will spawn entity only if count of nearby entities with same type will be lower than specified.
|
||||
*
|
||||
* @param count max entity count.
|
||||
* @param side side of box to search in.
|
||||
* @return same {@link SpawnRuleBuilder} instance.
|
||||
*/
|
||||
public SpawnRuleBuilder maxNearby(int count, int side) {
|
||||
return maxNearby(entityType, count, side);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will spawn entity only if count of nearby entities with same type will be lower than specified.
|
||||
*
|
||||
* @param count max entity count.
|
||||
* @return same {@link SpawnRuleBuilder} instance.
|
||||
*/
|
||||
public SpawnRuleBuilder maxNearby(int count) {
|
||||
return maxNearby(entityType, count, 256);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to add custom spawning rule for specific entities.
|
||||
*
|
||||
* @param rule {@link SpawnRule} rule, can be a lambda expression.
|
||||
* @return same {@link SpawnRuleBuilder} instance.
|
||||
*/
|
||||
public SpawnRuleBuilder customRule(SpawnRule rule) {
|
||||
rules.add(new SpawnRuleEntry(7, rule));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalize spawning rule creation.
|
||||
*
|
||||
* @param spawnType {@link Type} of spawn.
|
||||
* @param heightmapType {@link Types} heightmap type.
|
||||
*/
|
||||
public void build(Type spawnType, Types heightmapType) {
|
||||
final List<SpawnRuleEntry> rulesCopy = Lists.newArrayList(this.rules);
|
||||
Collections.sort(rulesCopy);
|
||||
|
||||
SpawnPredicate<M> predicate = (entityType, serverLevelAccessor, mobSpawnType, blockPos, random) -> {
|
||||
for (SpawnRuleEntry rule : rulesCopy) {
|
||||
if (!rule.canSpawn(entityType, serverLevelAccessor, mobSpawnType, blockPos, random)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
SpawnRestrictionAccessor.callRegister(entityType, spawnType, heightmapType, predicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalize spawning rule creation with No Restrictions spawn type, useful for flying entities.
|
||||
*
|
||||
* @param heightmapType {@link Types} heightmap type.
|
||||
*/
|
||||
public void buildNoRestrictions(Types heightmapType) {
|
||||
build(Type.NO_RESTRICTIONS, heightmapType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalize spawning rule creation with On Ground spawn type, useful for common entities.
|
||||
*
|
||||
* @param heightmapType {@link Types} heightmap type.
|
||||
*/
|
||||
public void buildOnGround(Types heightmapType) {
|
||||
build(Type.ON_GROUND, heightmapType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalize spawning rule creation with In Water spawn type, useful for water entities.
|
||||
*
|
||||
* @param heightmapType {@link Types} heightmap type.
|
||||
*/
|
||||
public void buildInWater(Types heightmapType) {
|
||||
build(Type.IN_WATER, heightmapType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalize spawning rule creation with In Lava spawn type, useful for lava entities.
|
||||
*
|
||||
* @param heightmapType {@link Types} heightmap type.
|
||||
*/
|
||||
public void buildInLava(Types heightmapType) {
|
||||
build(Type.IN_LAVA, heightmapType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function, will take entry from cache or create it if necessary.
|
||||
*
|
||||
* @param name {@link String} entry internal name.
|
||||
* @param supplier {@link Supplier} for {@link SpawnRuleEntry}.
|
||||
* @return new or existing {@link SpawnRuleEntry}.
|
||||
*/
|
||||
private static SpawnRuleEntry getFromCache(String name, Supplier<SpawnRuleEntry> supplier) {
|
||||
SpawnRuleEntry entry = RULES_CACHE.get(name);
|
||||
if (entry == null) {
|
||||
entry = supplier.get();
|
||||
RULES_CACHE.put(name, entry);
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package org.betterx.bclib.api.spawning;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.Mob;
|
||||
import net.minecraft.world.entity.MobSpawnType;
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
|
||||
import org.betterx.bclib.interfaces.SpawnRule;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class SpawnRuleEntry<M extends Mob> implements Comparable<SpawnRuleEntry> {
|
||||
private final SpawnRule rule;
|
||||
private final byte priority;
|
||||
|
||||
public SpawnRuleEntry(int priority, SpawnRule rule) {
|
||||
this.priority = (byte) priority;
|
||||
this.rule = rule;
|
||||
}
|
||||
|
||||
protected boolean canSpawn(EntityType<M> type,
|
||||
LevelAccessor world,
|
||||
MobSpawnType spawnReason,
|
||||
BlockPos pos,
|
||||
RandomSource random) {
|
||||
return rule.canSpawn(type, world, spawnReason, pos, random);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull SpawnRuleEntry entry) {
|
||||
return Integer.compare(priority, entry.priority);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,301 @@
|
|||
package org.betterx.bclib.api.surface;
|
||||
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.levelgen.SurfaceRules;
|
||||
import net.minecraft.world.level.levelgen.SurfaceRules.RuleSource;
|
||||
import net.minecraft.world.level.levelgen.placement.CaveSurface;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import org.betterx.bclib.api.biomes.BiomeAPI;
|
||||
import org.betterx.bclib.api.surface.rules.NoiseCondition;
|
||||
import org.betterx.bclib.world.surface.DoubleBlockSurfaceNoiseCondition;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class SurfaceRuleBuilder {
|
||||
private static final Map<String, SurfaceRuleEntry> RULES_CACHE = Maps.newHashMap();
|
||||
private static final SurfaceRuleBuilder INSTANCE = new SurfaceRuleBuilder();
|
||||
private final List<SurfaceRuleEntry> rules = Lists.newArrayList();
|
||||
private SurfaceRuleEntry entryInstance;
|
||||
private ResourceKey<Biome> biomeKey;
|
||||
|
||||
private SurfaceRuleBuilder() {
|
||||
}
|
||||
|
||||
public static SurfaceRuleBuilder start() {
|
||||
INSTANCE.biomeKey = null;
|
||||
INSTANCE.rules.clear();
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restricts surface to only one biome.
|
||||
*
|
||||
* @param biomeKey {@link ResourceKey} for the {@link Biome}.
|
||||
* @return same {@link SurfaceRuleBuilder} instance.
|
||||
*/
|
||||
public SurfaceRuleBuilder biome(ResourceKey<Biome> biomeKey) {
|
||||
this.biomeKey = biomeKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restricts surface to only one biome.
|
||||
*
|
||||
* @param biome {@link Biome}.
|
||||
* @return same {@link SurfaceRuleBuilder} instance.
|
||||
*/
|
||||
public SurfaceRuleBuilder biome(Biome biome) {
|
||||
return biome(BiomeAPI.getBiomeKey(biome));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set biome surface with specified {@link BlockState}. Example - block of grass in the Overworld biomes
|
||||
*
|
||||
* @param state {@link BlockState} for the ground cover.
|
||||
* @return same {@link SurfaceRuleBuilder} instance.
|
||||
*/
|
||||
public SurfaceRuleBuilder surface(BlockState state) {
|
||||
entryInstance = getFromCache("surface_" + state.toString(), () -> {
|
||||
RuleSource rule = SurfaceRules.state(state);
|
||||
rule = SurfaceRules.ifTrue(SurfaceRules.ON_FLOOR, rule);
|
||||
return new SurfaceRuleEntry(2, rule);
|
||||
});
|
||||
rules.add(entryInstance);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set biome subsurface with specified {@link BlockState}. Example - dirt in the Overworld biomes.
|
||||
*
|
||||
* @param state {@link BlockState} for the subterrain layer.
|
||||
* @param depth block layer depth.
|
||||
* @return same {@link SurfaceRuleBuilder} instance.
|
||||
*/
|
||||
public SurfaceRuleBuilder subsurface(BlockState state, int depth) {
|
||||
entryInstance = getFromCache("subsurface_" + depth + "_" + state.toString(), () -> {
|
||||
RuleSource rule = SurfaceRules.state(state);
|
||||
rule = SurfaceRules.ifTrue(SurfaceRules.stoneDepthCheck(depth, false, CaveSurface.FLOOR), rule);
|
||||
return new SurfaceRuleEntry(3, rule);
|
||||
});
|
||||
rules.add(entryInstance);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set biome filler with specified {@link BlockState}. Example - stone in the Overworld biomes. The rule is added with priority 10.
|
||||
*
|
||||
* @param state {@link BlockState} for filling.
|
||||
* @return same {@link SurfaceRuleBuilder} instance.
|
||||
*/
|
||||
public SurfaceRuleBuilder filler(BlockState state) {
|
||||
entryInstance = getFromCache("fill_" + state.toString(),
|
||||
() -> new SurfaceRuleEntry(10, SurfaceRules.state(state)));
|
||||
rules.add(entryInstance);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set biome floor with specified {@link BlockState}. Example - underside of a gravel floor. The rule is added with priority 3.
|
||||
*
|
||||
* @param state {@link BlockState} for the ground cover.
|
||||
* @return same {@link SurfaceRuleBuilder} instance.
|
||||
*/
|
||||
public SurfaceRuleBuilder floor(BlockState state) {
|
||||
entryInstance = getFromCache("floor_" + state.toString(), () -> {
|
||||
RuleSource rule = SurfaceRules.state(state);
|
||||
return new SurfaceRuleEntry(3, SurfaceRules.ifTrue(SurfaceRules.ON_FLOOR, rule));
|
||||
});
|
||||
rules.add(entryInstance);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set biome floor material with specified {@link BlockState} and height. The rule is added with priority 3.
|
||||
*
|
||||
* @param state {@link BlockState} for the subterrain layer.
|
||||
* @param height block layer height.
|
||||
* @param noise The noise object that is applied
|
||||
* @return same {@link SurfaceRuleBuilder} instance.
|
||||
*/
|
||||
public SurfaceRuleBuilder belowFloor(BlockState state, int height, NoiseCondition noise) {
|
||||
entryInstance = getFromCache("below_floor_" + height + "_" + state.toString() + "_" + noise.getClass()
|
||||
.getSimpleName(),
|
||||
() -> {
|
||||
RuleSource rule = SurfaceRules.state(state);
|
||||
rule = SurfaceRules.ifTrue(SurfaceRules.stoneDepthCheck(height,
|
||||
false,
|
||||
CaveSurface.FLOOR),
|
||||
SurfaceRules.ifTrue(noise, rule));
|
||||
return new SurfaceRuleEntry(3, rule);
|
||||
});
|
||||
rules.add(entryInstance);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set biome floor material with specified {@link BlockState} and height. The rule is added with priority 3.
|
||||
*
|
||||
* @param state {@link BlockState} for the subterrain layer.
|
||||
* @param height block layer height.
|
||||
* @return same {@link SurfaceRuleBuilder} instance.
|
||||
*/
|
||||
public SurfaceRuleBuilder belowFloor(BlockState state, int height) {
|
||||
entryInstance = getFromCache("below_floor_" + height + "_" + state.toString(), () -> {
|
||||
RuleSource rule = SurfaceRules.state(state);
|
||||
rule = SurfaceRules.ifTrue(SurfaceRules.stoneDepthCheck(height, false, CaveSurface.FLOOR), rule);
|
||||
return new SurfaceRuleEntry(3, rule);
|
||||
});
|
||||
rules.add(entryInstance);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set biome ceiling with specified {@link BlockState}. Example - block of sandstone in the Overworld desert in air pockets. The rule is added with priority 3.
|
||||
*
|
||||
* @param state {@link BlockState} for the ground cover.
|
||||
* @return same {@link SurfaceRuleBuilder} instance.
|
||||
*/
|
||||
public SurfaceRuleBuilder ceil(BlockState state) {
|
||||
entryInstance = getFromCache("ceil_" + state.toString(), () -> {
|
||||
RuleSource rule = SurfaceRules.state(state);
|
||||
return new SurfaceRuleEntry(3, SurfaceRules.ifTrue(SurfaceRules.ON_CEILING, rule));
|
||||
});
|
||||
rules.add(entryInstance);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set biome ceiling material with specified {@link BlockState} and height. Example - sandstone in the Overworld deserts. The rule is added with priority 3.
|
||||
*
|
||||
* @param state {@link BlockState} for the subterrain layer.
|
||||
* @param height block layer height.
|
||||
* @return same {@link SurfaceRuleBuilder} instance.
|
||||
*/
|
||||
public SurfaceRuleBuilder aboveCeil(BlockState state, int height) {
|
||||
entryInstance = getFromCache("above_ceil_" + height + "_" + state.toString(), () -> {
|
||||
RuleSource rule = SurfaceRules.state(state);
|
||||
rule = SurfaceRules.ifTrue(SurfaceRules.stoneDepthCheck(height, false, CaveSurface.CEILING), rule);
|
||||
return new SurfaceRuleEntry(3, rule);
|
||||
});
|
||||
rules.add(entryInstance);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will cover steep areas (with large terrain angle). Example - Overworld mountains.
|
||||
*
|
||||
* @param state {@link BlockState} for the steep layer.
|
||||
* @param depth layer depth
|
||||
* @return
|
||||
*/
|
||||
public SurfaceRuleBuilder steep(BlockState state, int depth) {
|
||||
entryInstance = getFromCache("steep_" + depth + "_" + state.toString(), () -> {
|
||||
RuleSource rule = SurfaceRules.state(state);
|
||||
rule = SurfaceRules.ifTrue(SurfaceRules.stoneDepthCheck(depth, false, CaveSurface.FLOOR), rule);
|
||||
rule = SurfaceRules.ifTrue(SurfaceRules.steep(), rule);
|
||||
int priority = depth < 1 ? 0 : 1;
|
||||
return new SurfaceRuleEntry(priority, rule);
|
||||
});
|
||||
rules.add(entryInstance);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to add custom rule.
|
||||
*
|
||||
* @param priority rule priority, lower values = higher priority (rule will be applied before others).
|
||||
* @param rule custom {@link SurfaceRules.RuleSource}.
|
||||
* @return same {@link SurfaceRuleBuilder} instance.
|
||||
*/
|
||||
public SurfaceRuleBuilder rule(int priority, SurfaceRules.RuleSource rule) {
|
||||
rules.add(new SurfaceRuleEntry(priority, rule));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to add custom rule.
|
||||
*
|
||||
* @param rule custom {@link SurfaceRules.RuleSource}.
|
||||
* @return same {@link SurfaceRuleBuilder} instance.
|
||||
*/
|
||||
public SurfaceRuleBuilder rule(SurfaceRules.RuleSource rule) {
|
||||
return rule(7, rule);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set biome floor with specified {@link BlockState} and the {@link DoubleBlockSurfaceNoiseCondition}. The rule is added with priority 3.
|
||||
*
|
||||
* @param surfaceBlockA {@link BlockState} for the ground cover.
|
||||
* @param surfaceBlockB {@link BlockState} for the alternative ground cover.
|
||||
* @return same {@link SurfaceRuleBuilder} instance.
|
||||
*/
|
||||
public SurfaceRuleBuilder chancedFloor(BlockState surfaceBlockA, BlockState surfaceBlockB) {
|
||||
return chancedFloor(surfaceBlockA, surfaceBlockB, DoubleBlockSurfaceNoiseCondition.CONDITION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set biome floor with specified {@link BlockState} and the given Noise Function. The rule is added with priority 3.
|
||||
*
|
||||
* @param surfaceBlockA {@link BlockState} for the ground cover.
|
||||
* @param surfaceBlockB {@link BlockState} for the alternative ground cover.
|
||||
* @param noise The {@link NoiseCondition}
|
||||
* @return same {@link SurfaceRuleBuilder} instance.
|
||||
*/
|
||||
public SurfaceRuleBuilder chancedFloor(BlockState surfaceBlockA, BlockState surfaceBlockB, NoiseCondition noise) {
|
||||
entryInstance = getFromCache("chancedFloor_" + surfaceBlockA + "_" + surfaceBlockB + "_" + noise.getClass()
|
||||
.getSimpleName(),
|
||||
() -> {
|
||||
RuleSource rule =
|
||||
SurfaceRules.ifTrue(SurfaceRules.ON_FLOOR,
|
||||
SurfaceRules.sequence(
|
||||
SurfaceRules.ifTrue(noise,
|
||||
SurfaceRules.state(
|
||||
surfaceBlockA)),
|
||||
SurfaceRules.state(surfaceBlockB)
|
||||
)
|
||||
);
|
||||
return new SurfaceRuleEntry(4, rule);
|
||||
});
|
||||
rules.add(entryInstance);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalise rule building process.
|
||||
*
|
||||
* @return {@link SurfaceRules.RuleSource}.
|
||||
*/
|
||||
public SurfaceRules.RuleSource build() {
|
||||
Collections.sort(rules);
|
||||
List<SurfaceRules.RuleSource> ruleList = rules.stream().map(entry -> entry.getRule()).toList();
|
||||
SurfaceRules.RuleSource[] ruleArray = ruleList.toArray(new SurfaceRules.RuleSource[ruleList.size()]);
|
||||
SurfaceRules.RuleSource rule = SurfaceRules.sequence(ruleArray);
|
||||
if (biomeKey != null) {
|
||||
rule = SurfaceRules.ifTrue(SurfaceRules.isBiome(biomeKey), rule);
|
||||
}
|
||||
return rule;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function, will take entry from cache or create it if necessary.
|
||||
*
|
||||
* @param name {@link String} entry internal name.
|
||||
* @param supplier {@link Supplier} for {@link SurfaceRuleEntry}.
|
||||
* @return new or existing {@link SurfaceRuleEntry}.
|
||||
*/
|
||||
private static SurfaceRuleEntry getFromCache(String name, Supplier<SurfaceRuleEntry> supplier) {
|
||||
SurfaceRuleEntry entry = RULES_CACHE.get(name);
|
||||
if (entry == null) {
|
||||
entry = supplier.get();
|
||||
RULES_CACHE.put(name, entry);
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package org.betterx.bclib.api.surface;
|
||||
|
||||
import net.minecraft.world.entity.Mob;
|
||||
import net.minecraft.world.level.levelgen.SurfaceRules;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class SurfaceRuleEntry<M extends Mob> implements Comparable<SurfaceRuleEntry> {
|
||||
private final SurfaceRules.RuleSource rule;
|
||||
private final byte priority;
|
||||
|
||||
public SurfaceRuleEntry(int priority, SurfaceRules.RuleSource rule) {
|
||||
this.priority = (byte) priority;
|
||||
this.rule = rule;
|
||||
}
|
||||
|
||||
protected SurfaceRules.RuleSource getRule() {
|
||||
return rule;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull SurfaceRuleEntry entry) {
|
||||
return Integer.compare(priority, entry.priority);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package org.betterx.bclib.api.surface.rules;
|
||||
|
||||
import net.minecraft.world.level.levelgen.SurfaceRules;
|
||||
|
||||
import org.betterx.bclib.mixin.common.SurfaceRulesContextAccessor;
|
||||
|
||||
public interface NoiseCondition extends SurfaceRules.ConditionSource {
|
||||
boolean test(SurfaceRulesContextAccessor context);
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package org.betterx.bclib.api.surface.rules;
|
||||
|
||||
import net.minecraft.core.Registry;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.interfaces.NumericProvider;
|
||||
import org.betterx.bclib.mixin.common.SurfaceRulesContextAccessor;
|
||||
import org.betterx.bclib.util.MHelper;
|
||||
|
||||
public record RandomIntProvider(int range) implements NumericProvider {
|
||||
public static final Codec<RandomIntProvider> CODEC = Codec.INT.fieldOf("range")
|
||||
.xmap(RandomIntProvider::new, obj -> obj.range)
|
||||
.codec();
|
||||
|
||||
@Override
|
||||
public int getNumber(SurfaceRulesContextAccessor context) {
|
||||
return MHelper.RANDOM.nextInt(range);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Codec<? extends NumericProvider> pcodec() {
|
||||
return CODEC;
|
||||
}
|
||||
|
||||
static {
|
||||
Registry.register(NumericProvider.NUMERIC_PROVIDER, BCLib.makeID("rnd_int"), RandomIntProvider.CODEC);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package org.betterx.bclib.api.surface.rules;
|
||||
|
||||
import net.minecraft.world.level.levelgen.SurfaceRules.Condition;
|
||||
import net.minecraft.world.level.levelgen.SurfaceRules.Context;
|
||||
import net.minecraft.world.level.levelgen.SurfaceRules.LazyXZCondition;
|
||||
|
||||
import org.betterx.bclib.mixin.common.SurfaceRulesContextAccessor;
|
||||
|
||||
public abstract class SurfaceNoiseCondition implements NoiseCondition {
|
||||
@Override
|
||||
public final Condition apply(Context context2) {
|
||||
final SurfaceNoiseCondition self = this;
|
||||
|
||||
class Generator extends LazyXZCondition {
|
||||
Generator() {
|
||||
super(context2);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean compute() {
|
||||
final SurfaceRulesContextAccessor context = SurfaceRulesContextAccessor.class.cast(this.context);
|
||||
if (context == null) return false;
|
||||
return self.test(context);
|
||||
}
|
||||
}
|
||||
|
||||
return new Generator();
|
||||
}
|
||||
|
||||
public abstract boolean test(SurfaceRulesContextAccessor context);
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package org.betterx.bclib.api.surface.rules;
|
||||
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.util.KeyDispatchDataCodec;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.levelgen.SurfaceRules.Context;
|
||||
import net.minecraft.world.level.levelgen.SurfaceRules.RuleSource;
|
||||
import net.minecraft.world.level.levelgen.SurfaceRules.SurfaceRule;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
import org.betterx.bclib.interfaces.NumericProvider;
|
||||
import org.betterx.bclib.mixin.common.SurfaceRulesContextAccessor;
|
||||
|
||||
import java.util.List;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
//
|
||||
public record SwitchRuleSource(NumericProvider selector, List<RuleSource> collection) implements RuleSource {
|
||||
public static final Codec<SwitchRuleSource> CODEC = RecordCodecBuilder.create(instance -> instance.group(
|
||||
NumericProvider.CODEC.fieldOf("selector").forGetter(SwitchRuleSource::selector),
|
||||
RuleSource.CODEC.listOf().fieldOf("collection").forGetter(SwitchRuleSource::collection)
|
||||
)
|
||||
.apply(instance,
|
||||
SwitchRuleSource::new));
|
||||
|
||||
private static final KeyDispatchDataCodec<? extends RuleSource> KEY_CODEC = KeyDispatchDataCodec.of(SwitchRuleSource.CODEC);
|
||||
|
||||
@Override
|
||||
public KeyDispatchDataCodec<? extends RuleSource> codec() {
|
||||
return KEY_CODEC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SurfaceRule apply(Context context) {
|
||||
|
||||
return new SurfaceRule() {
|
||||
@Nullable
|
||||
@Override
|
||||
public BlockState tryApply(int x, int y, int z) {
|
||||
final SurfaceRulesContextAccessor ctx = SurfaceRulesContextAccessor.class.cast(context);
|
||||
int nr = Math.max(0, selector.getNumber(ctx)) % collection.size();
|
||||
|
||||
return collection.get(nr).apply(context).tryApply(x, y, z);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static {
|
||||
Registry.register(Registry.RULE, "bclib_switch_rule", SwitchRuleSource.CODEC);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package org.betterx.bclib.api.surface.rules;
|
||||
|
||||
import net.minecraft.util.KeyDispatchDataCodec;
|
||||
import net.minecraft.world.level.levelgen.SurfaceRules.Condition;
|
||||
import net.minecraft.world.level.levelgen.SurfaceRules.ConditionSource;
|
||||
import net.minecraft.world.level.levelgen.SurfaceRules.Context;
|
||||
import net.minecraft.world.level.levelgen.SurfaceRules.LazyCondition;
|
||||
|
||||
import org.betterx.bclib.mixin.common.SurfaceRulesContextAccessor;
|
||||
|
||||
public abstract class VolumeNoiseCondition implements NoiseCondition {
|
||||
public abstract KeyDispatchDataCodec<? extends ConditionSource> codec();
|
||||
|
||||
@Override
|
||||
public final Condition apply(Context context2) {
|
||||
final VolumeNoiseCondition self = this;
|
||||
|
||||
class Generator extends LazyCondition {
|
||||
Generator() {
|
||||
super(context2);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getContextLastUpdate() {
|
||||
final SurfaceRulesContextAccessor ctx = SurfaceRulesContextAccessor.class.cast(this.context);
|
||||
return ctx.getLastUpdateY() + ctx.getLastUpdateXZ();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean compute() {
|
||||
final SurfaceRulesContextAccessor context = SurfaceRulesContextAccessor.class.cast(this.context);
|
||||
if (context == null) return false;
|
||||
return self.test(context);
|
||||
}
|
||||
}
|
||||
|
||||
return new Generator();
|
||||
}
|
||||
|
||||
public abstract boolean test(SurfaceRulesContextAccessor context);
|
||||
}
|
|
@ -1,8 +1,7 @@
|
|||
package ru.bclib.api.tag;
|
||||
package org.betterx.bclib.api.tag;
|
||||
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
|
||||
public class CommonBiomeTags {
|
||||
public static final TagKey<Biome> IN_NETHER = TagAPI.makeCommonBiomeTag("in_nether");
|
27
src/main/java/org/betterx/bclib/api/tag/CommonBlockTags.java
Normal file
27
src/main/java/org/betterx/bclib/api/tag/CommonBlockTags.java
Normal file
|
@ -0,0 +1,27 @@
|
|||
package org.betterx.bclib.api.tag;
|
||||
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
|
||||
public class CommonBlockTags {
|
||||
public static final TagKey<Block> BARREL = TagAPI.makeCommonBlockTag("barrel");
|
||||
public static final TagKey<Block> BOOKSHELVES = TagAPI.makeCommonBlockTag("bookshelves");
|
||||
public static final TagKey<Block> CHEST = TagAPI.makeCommonBlockTag("chest");
|
||||
public static final TagKey<Block> END_STONES = TagAPI.makeCommonBlockTag("end_stones");
|
||||
public static final TagKey<Block> GEN_END_STONES = END_STONES;
|
||||
public static final TagKey<Block> IMMOBILE = TagAPI.makeCommonBlockTag("immobile");
|
||||
public static final TagKey<Block> LEAVES = TagAPI.makeCommonBlockTag("leaves");
|
||||
public static final TagKey<Block> NETHERRACK = TagAPI.makeCommonBlockTag("netherrack");
|
||||
public static final TagKey<Block> NETHER_MYCELIUM = TagAPI.makeCommonBlockTag("nether_mycelium");
|
||||
public static final TagKey<Block> NETHER_PORTAL_FRAME = TagAPI.makeCommonBlockTag("nether_pframe");
|
||||
public static final TagKey<Block> NETHER_STONES = TagAPI.makeCommonBlockTag("nether_stones");
|
||||
public static final TagKey<Block> SAPLINGS = TagAPI.makeCommonBlockTag("saplings");
|
||||
public static final TagKey<Block> SOUL_GROUND = TagAPI.makeCommonBlockTag("soul_ground");
|
||||
public static final TagKey<Block> WOODEN_BARREL = TagAPI.makeCommonBlockTag("wooden_barrels");
|
||||
public static final TagKey<Block> WOODEN_CHEST = TagAPI.makeCommonBlockTag("wooden_chests");
|
||||
public static final TagKey<Block> WORKBENCHES = TagAPI.makeCommonBlockTag("workbench");
|
||||
|
||||
public static final TagKey<Block> DRAGON_IMMUNE = TagAPI.makeCommonBlockTag("dragon_immune");
|
||||
|
||||
public static final TagKey<Block> MINABLE_WITH_HAMMER = TagAPI.makeCommonBlockTag("mineable/hammer");
|
||||
}
|
19
src/main/java/org/betterx/bclib/api/tag/CommonItemTags.java
Normal file
19
src/main/java/org/betterx/bclib/api/tag/CommonItemTags.java
Normal file
|
@ -0,0 +1,19 @@
|
|||
package org.betterx.bclib.api.tag;
|
||||
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.item.Item;
|
||||
|
||||
public class CommonItemTags {
|
||||
public final static TagKey<Item> HAMMERS = TagAPI.makeCommonItemTag("hammers");
|
||||
public static final TagKey<Item> BARREL = TagAPI.makeCommonItemTag("barrel");
|
||||
public static final TagKey<Item> CHEST = TagAPI.makeCommonItemTag("chest");
|
||||
public static final TagKey<Item> SHEARS = TagAPI.makeCommonItemTag("shears");
|
||||
public static final TagKey<Item> FURNACES = TagAPI.makeCommonItemTag("furnaces");
|
||||
public static final TagKey<Item> IRON_INGOTS = TagAPI.makeCommonItemTag("iron_ingots");
|
||||
public static final TagKey<Item> LEAVES = TagAPI.makeCommonItemTag("leaves");
|
||||
public static final TagKey<Item> SAPLINGS = TagAPI.makeCommonItemTag("saplings");
|
||||
public static final TagKey<Item> SOUL_GROUND = TagAPI.makeCommonItemTag("soul_ground");
|
||||
public static final TagKey<Item> WOODEN_BARREL = TagAPI.makeCommonItemTag("wooden_barrels");
|
||||
public static final TagKey<Item> WOODEN_CHEST = TagAPI.makeCommonItemTag("wooden_chests");
|
||||
public static final TagKey<Item> WORKBENCHES = TagAPI.makeCommonItemTag("workbench");
|
||||
}
|
39
src/main/java/org/betterx/bclib/api/tag/NamedBlockTags.java
Normal file
39
src/main/java/org/betterx/bclib/api/tag/NamedBlockTags.java
Normal file
|
@ -0,0 +1,39 @@
|
|||
package org.betterx.bclib.api.tag;
|
||||
|
||||
import net.minecraft.tags.BlockTags;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
|
||||
|
||||
public class NamedBlockTags {
|
||||
public static final TagKey<Block> ANVIL = BlockTags.ANVIL;
|
||||
public static final TagKey<Block> BUTTONS = BlockTags.BUTTONS;
|
||||
public static final TagKey<Block> CLIMBABLE = BlockTags.CLIMBABLE;
|
||||
public static final TagKey<Block> DOORS = BlockTags.DOORS;
|
||||
public static final TagKey<Block> FENCES = BlockTags.FENCES;
|
||||
public static final TagKey<Block> FENCE_GATES = BlockTags.FENCE_GATES;
|
||||
public static final TagKey<Block> LEAVES = BlockTags.LEAVES;
|
||||
public static final TagKey<Block> LOGS = BlockTags.LOGS;
|
||||
public static final TagKey<Block> LOGS_THAT_BURN = BlockTags.LOGS_THAT_BURN;
|
||||
public static final TagKey<Block> NYLIUM = BlockTags.NYLIUM;
|
||||
public static final TagKey<Block> PLANKS = BlockTags.PLANKS;
|
||||
public static final TagKey<Block> PRESSURE_PLATES = BlockTags.PRESSURE_PLATES;
|
||||
public static final TagKey<Block> SAPLINGS = BlockTags.SAPLINGS;
|
||||
public static final TagKey<Block> SIGNS = BlockTags.SIGNS;
|
||||
public static final TagKey<Block> SLABS = BlockTags.SLABS;
|
||||
public static final TagKey<Block> STAIRS = BlockTags.STAIRS;
|
||||
public static final TagKey<Block> STONE_PRESSURE_PLATES = BlockTags.STONE_PRESSURE_PLATES;
|
||||
public static final TagKey<Block> TRAPDOORS = BlockTags.TRAPDOORS;
|
||||
public static final TagKey<Block> WALLS = BlockTags.WALLS;
|
||||
public static final TagKey<Block> WOODEN_BUTTONS = BlockTags.WOODEN_BUTTONS;
|
||||
public static final TagKey<Block> WOODEN_DOORS = BlockTags.WOODEN_DOORS;
|
||||
public static final TagKey<Block> WOODEN_FENCES = BlockTags.WOODEN_FENCES;
|
||||
public static final TagKey<Block> WOODEN_PRESSURE_PLATES = BlockTags.WOODEN_PRESSURE_PLATES;
|
||||
public static final TagKey<Block> WOODEN_SLABS = BlockTags.WOODEN_SLABS;
|
||||
public static final TagKey<Block> WOODEN_STAIRS = BlockTags.WOODEN_STAIRS;
|
||||
public static final TagKey<Block> WOODEN_TRAPDOORS = BlockTags.WOODEN_TRAPDOORS;
|
||||
public static final TagKey<Block> SOUL_FIRE_BASE_BLOCKS = BlockTags.SOUL_FIRE_BASE_BLOCKS;
|
||||
public static final TagKey<Block> SOUL_SPEED_BLOCKS = BlockTags.SOUL_SPEED_BLOCKS;
|
||||
public static final TagKey<Block> BEACON_BASE_BLOCKS = BlockTags.BEACON_BASE_BLOCKS;
|
||||
public static final TagKey<Block> STONE_BRICKS = BlockTags.STONE_BRICKS;
|
||||
}
|
32
src/main/java/org/betterx/bclib/api/tag/NamedItemTags.java
Normal file
32
src/main/java/org/betterx/bclib/api/tag/NamedItemTags.java
Normal file
|
@ -0,0 +1,32 @@
|
|||
package org.betterx.bclib.api.tag;
|
||||
|
||||
import net.minecraft.tags.ItemTags;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.item.Item;
|
||||
|
||||
|
||||
public class NamedItemTags {
|
||||
public static final TagKey<Item> BUTTONS = ItemTags.BUTTONS;
|
||||
public static final TagKey<Item> DOORS = ItemTags.DOORS;
|
||||
public static final TagKey<Item> FENCES = ItemTags.FENCES;
|
||||
public static final TagKey<Item> LEAVES = ItemTags.LEAVES;
|
||||
public static final TagKey<Item> LOGS = ItemTags.LOGS;
|
||||
public static final TagKey<Item> LOGS_THAT_BURN = ItemTags.LOGS_THAT_BURN;
|
||||
public static final TagKey<Item> PLANKS = ItemTags.PLANKS;
|
||||
public static final TagKey<Item> SAPLINGS = ItemTags.SAPLINGS;
|
||||
public static final TagKey<Item> SIGNS = ItemTags.SIGNS;
|
||||
public static final TagKey<Item> SLABS = ItemTags.SLABS;
|
||||
public static final TagKey<Item> STAIRS = ItemTags.STAIRS;
|
||||
public static final TagKey<Item> TRAPDOORS = ItemTags.TRAPDOORS;
|
||||
public static final TagKey<Item> WOODEN_BUTTONS = ItemTags.WOODEN_BUTTONS;
|
||||
public static final TagKey<Item> WOODEN_DOORS = ItemTags.WOODEN_DOORS;
|
||||
public static final TagKey<Item> WOODEN_FENCES = ItemTags.WOODEN_FENCES;
|
||||
public static final TagKey<Item> WOODEN_PRESSURE_PLATES = ItemTags.WOODEN_PRESSURE_PLATES;
|
||||
public static final TagKey<Item> WOODEN_SLABS = ItemTags.WOODEN_SLABS;
|
||||
public static final TagKey<Item> WOODEN_STAIRS = ItemTags.WOODEN_STAIRS;
|
||||
public static final TagKey<Item> WOODEN_TRAPDOORS = ItemTags.WOODEN_TRAPDOORS;
|
||||
public static final TagKey<Item> BEACON_PAYMENT_ITEMS = ItemTags.BEACON_PAYMENT_ITEMS;
|
||||
public static final TagKey<Item> STONE_BRICKS = ItemTags.STONE_BRICKS;
|
||||
public static final TagKey<Item> STONE_CRAFTING_MATERIALS = ItemTags.STONE_CRAFTING_MATERIALS;
|
||||
public static final TagKey<Item> STONE_TOOL_MATERIALS = ItemTags.STONE_TOOL_MATERIALS;
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package org.betterx.bclib.api.tag;
|
||||
|
||||
import net.minecraft.tags.BlockTags;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
|
||||
|
||||
public class NamedMineableTags {
|
||||
public static final TagKey<Block> AXE = BlockTags.MINEABLE_WITH_AXE;
|
||||
public static final TagKey<Block> HOE = BlockTags.MINEABLE_WITH_HOE;
|
||||
public static final TagKey<Block> PICKAXE = BlockTags.MINEABLE_WITH_PICKAXE;
|
||||
public static final TagKey<Block> SHEARS = TagAPI.makeBlockTag("fabric", "mineable/shears");
|
||||
public static final TagKey<Block> SHOVEL = BlockTags.MINEABLE_WITH_SHOVEL;
|
||||
public static final TagKey<Block> SWORD = TagAPI.makeBlockTag("fabric", "mineable/sword");
|
||||
public static final TagKey<Block> HAMMER = TagAPI.makeCommonBlockTag("mineable/hammer");
|
||||
}
|
14
src/main/java/org/betterx/bclib/api/tag/NamedToolTags.java
Normal file
14
src/main/java/org/betterx/bclib/api/tag/NamedToolTags.java
Normal file
|
@ -0,0 +1,14 @@
|
|||
package org.betterx.bclib.api.tag;
|
||||
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.item.Item;
|
||||
|
||||
|
||||
public class NamedToolTags {
|
||||
public static final TagKey<Item> FABRIC_AXES = TagAPI.makeItemTag("fabric", "axes");
|
||||
public static final TagKey<Item> FABRIC_HOES = TagAPI.makeItemTag("fabric", "hoes");
|
||||
public static final TagKey<Item> FABRIC_PICKAXES = TagAPI.makeItemTag("fabric", "pickaxes");
|
||||
public static final TagKey<Item> FABRIC_SHEARS = TagAPI.makeItemTag("fabric", "shears");
|
||||
public static final TagKey<Item> FABRIC_SHOVELS = TagAPI.makeItemTag("fabric", "shovels");
|
||||
public static final TagKey<Item> FABRIC_SWORDS = TagAPI.makeItemTag("fabric", "swords");
|
||||
}
|
277
src/main/java/org/betterx/bclib/api/tag/TagAPI.java
Normal file
277
src/main/java/org/betterx/bclib/api/tag/TagAPI.java
Normal file
|
@ -0,0 +1,277 @@
|
|||
package org.betterx.bclib.api.tag;
|
||||
|
||||
import net.minecraft.core.DefaultedRegistry;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
import net.minecraft.tags.Tag;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.level.ItemLike;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import org.betterx.bclib.api.biomes.BiomeAPI;
|
||||
import org.betterx.bclib.mixin.common.DiggerItemAccessor;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class TagAPI {
|
||||
private static final Map<String, TagType<?>> TYPES = Maps.newHashMap();
|
||||
|
||||
public static TagType.RegistryBacked<Block> BLOCKS = registerType(Registry.BLOCK);
|
||||
public static TagType.RegistryBacked<Item> ITEMS = registerType(Registry.ITEM);
|
||||
public static TagType.Simple<Biome> BIOMES = registerType(Registry.BIOME_REGISTRY,
|
||||
"tags/worldgen/biome",
|
||||
b -> BiomeAPI.getBiomeID(b));
|
||||
|
||||
private static <T> TagType.RegistryBacked<T> registerType(DefaultedRegistry<T> registry) {
|
||||
TagType<T> type = new TagType.RegistryBacked<>(registry);
|
||||
return (TagType.RegistryBacked<T>) TYPES.computeIfAbsent(type.directory, (dir) -> type);
|
||||
}
|
||||
|
||||
public static <T> TagType.Simple<T> registerType(ResourceKey<? extends Registry<T>> registry,
|
||||
String directory,
|
||||
Function<T, ResourceLocation> locationProvider) {
|
||||
return (TagType.Simple<T>) TYPES.computeIfAbsent(directory,
|
||||
(dir) -> new TagType.Simple<>(registry,
|
||||
dir,
|
||||
locationProvider));
|
||||
}
|
||||
|
||||
public static <T> TagType.UnTyped<T> registerType(ResourceKey<? extends Registry<T>> registry, String directory) {
|
||||
return (TagType.UnTyped<T>) TYPES.computeIfAbsent(directory, (dir) -> new TagType.UnTyped<>(registry, dir));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create {@link Block} {@link TagKey} with mod namespace.
|
||||
*
|
||||
* @param modID - {@link String} mod namespace (mod id);
|
||||
* @param name - {@link String} tag name.
|
||||
* @return {@link Block} {@link TagKey}.
|
||||
*/
|
||||
public static TagKey<Biome> makeBiomeTag(String modID, String name) {
|
||||
return BIOMES.makeTag(new ResourceLocation(modID, name));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get or create {@link Block} {@link TagKey} with mod namespace.
|
||||
*
|
||||
* @param modID - {@link String} mod namespace (mod id);
|
||||
* @param name - {@link String} tag name.
|
||||
* @return {@link Block} {@link TagKey}.
|
||||
*/
|
||||
public static TagKey<Block> makeBlockTag(String modID, String name) {
|
||||
return BLOCKS.makeTag(new ResourceLocation(modID, name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create {@link Block} {@link TagKey} with mod namespace.
|
||||
*
|
||||
* @param id - {@link String} id for the tag;
|
||||
* @return {@link Block} {@link TagKey}.
|
||||
*/
|
||||
public static TagKey<Block> makeBlockTag(ResourceLocation id) {
|
||||
return BLOCKS.makeTag(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create {@link Item} {@link TagKey} with mod namespace.
|
||||
*
|
||||
* @param modID - {@link String} mod namespace (mod id);
|
||||
* @param name - {@link String} tag name.
|
||||
* @return {@link Item} {@link TagKey}.
|
||||
*/
|
||||
public static TagKey<Item> makeItemTag(String modID, String name) {
|
||||
return ITEMS.makeTag(new ResourceLocation(modID, name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create {@link Item} {@link TagKey} with mod namespace.
|
||||
*
|
||||
* @param id - {@link String} id for the tag;
|
||||
* @return {@link Item} {@link TagKey}.
|
||||
*/
|
||||
public static TagKey<Item> makeItemTag(ResourceLocation id) {
|
||||
return ITEMS.makeTag(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create {@link Block} {@link TagKey}.
|
||||
*
|
||||
* @param name - {@link String} tag name.
|
||||
* @return {@link Block} {@link TagKey}.
|
||||
* @see <a href="https://fabricmc.net/wiki/tutorial:tags">Fabric Wiki (Tags)</a>
|
||||
*/
|
||||
public static TagKey<Block> makeCommonBlockTag(String name) {
|
||||
return BLOCKS.makeCommonTag(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create {@link Item} {@link TagKey}.
|
||||
*
|
||||
* @param name - {@link String} tag name.
|
||||
* @return {@link Item} {@link TagKey}.
|
||||
* @see <a href="https://fabricmc.net/wiki/tutorial:tags">Fabric Wiki (Tags)</a>
|
||||
*/
|
||||
public static TagKey<Item> makeCommonItemTag(String name) {
|
||||
return ITEMS.makeCommonTag(name);
|
||||
}
|
||||
|
||||
public static TagKey<Biome> makeCommonBiomeTag(String name) {
|
||||
return BIOMES.makeCommonTag(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes basic tags. Should be called only in BCLib main class.
|
||||
*/
|
||||
public static void init() {
|
||||
addBlockTag(CommonBlockTags.BOOKSHELVES, Blocks.BOOKSHELF);
|
||||
addBlockTag(CommonBlockTags.CHEST, Blocks.CHEST);
|
||||
addItemTag(CommonItemTags.CHEST, Items.CHEST);
|
||||
addItemTag(CommonItemTags.IRON_INGOTS, Items.IRON_INGOT);
|
||||
addItemTag(CommonItemTags.FURNACES, Blocks.FURNACE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds multiple Tags to one Biome.
|
||||
*
|
||||
* @param tagIDs array of {@link TagKey<Biome>} tag IDs.
|
||||
* @param biome The {@link Biome} to add tag.
|
||||
*/
|
||||
@SafeVarargs
|
||||
@Deprecated(forRemoval = true)
|
||||
public static void addBiomeTags(Biome biome, TagKey<Biome>... tagIDs) {
|
||||
BIOMES.add(biome, tagIDs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds one Tag to multiple Biomes.
|
||||
*
|
||||
* @param tagID {@link TagKey<Biome>} tag ID.
|
||||
* @param biomes array of {@link Biome} to add into tag.
|
||||
*/
|
||||
public static void addBiomeTag(TagKey<Biome> tagID, Biome... biomes) {
|
||||
BIOMES.add(tagID, biomes);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds multiple Tags to one Block.
|
||||
*
|
||||
* @param tagIDs array of {@link TagKey<Block>} tag IDs.
|
||||
* @param block The {@link Block} to add tag.
|
||||
*/
|
||||
@SafeVarargs
|
||||
@Deprecated(forRemoval = true)
|
||||
public static void addBlockTags(Block block, TagKey<Block>... tagIDs) {
|
||||
BLOCKS.add(block, tagIDs);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds one Tag to multiple Blocks.
|
||||
*
|
||||
* @param tagID {@link TagKey<Block>} tag ID.
|
||||
* @param blocks array of {@link Block} to add into tag.
|
||||
*/
|
||||
public static void addBlockTag(TagKey<Block> tagID, Block... blocks) {
|
||||
BLOCKS.add(tagID, blocks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds multiple Tags to one Item.
|
||||
*
|
||||
* @param tagIDs array of {@link TagKey<Item>} tag IDs.
|
||||
* @param item The {@link Item} to add tag.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
@SafeVarargs
|
||||
public static void addItemTags(ItemLike item, TagKey<Item>... tagIDs) {
|
||||
ITEMS.add(item.asItem(), tagIDs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds one Tag to multiple Items.
|
||||
*
|
||||
* @param tagID {@link TagKey<Item>} tag ID.
|
||||
* @param items array of {@link ItemLike} to add into tag.
|
||||
*/
|
||||
public static void addItemTag(TagKey<Item> tagID, ItemLike... items) {
|
||||
for (ItemLike i : items) {
|
||||
ITEMS.add(i.asItem(), tagID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds one Tag to multiple Items.
|
||||
*
|
||||
* @param tagID {@link TagKey<Item>} tag ID.
|
||||
* @param items array of {@link ItemLike} to add into tag.
|
||||
*/
|
||||
public static void addItemTag(TagKey<Item> tagID, Item... items) {
|
||||
ITEMS.add(tagID, items);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Automatically called in {@link net.minecraft.tags.TagLoader#loadAndBuild(ResourceManager)}.
|
||||
* <p>
|
||||
* In most cases there is no need to call this Method manually.
|
||||
*
|
||||
* @param directory The name of the Tag-directory. Should be either <i>"tags/blocks"</i> or
|
||||
* <i>"tags/items"</i>.
|
||||
* @param tagsMap The map that will hold the registered Tags
|
||||
* @return The {@code tagsMap} Parameter.
|
||||
*/
|
||||
public static <T> Map<ResourceLocation, Tag.Builder> apply(String directory,
|
||||
Map<ResourceLocation, Tag.Builder> tagsMap) {
|
||||
|
||||
TagType<?> type = TYPES.get(directory);
|
||||
if (type != null) {
|
||||
type.apply(tagsMap);
|
||||
}
|
||||
|
||||
// final BiConsumer<ResourceLocation, Set<ResourceLocation>> consumer;
|
||||
// consumer = (id, ids) -> apply(tagsMap.computeIfAbsent(id, key -> Tag.Builder.tag()), ids);
|
||||
//
|
||||
// if ("tags/blocks".equals(directory)) {
|
||||
// TAGS_BLOCK.forEach(consumer);
|
||||
// }
|
||||
// else if ("tags/items".equals(directory)) {
|
||||
// TAGS_ITEM.forEach(consumer);
|
||||
// }
|
||||
// else if ("tags/worldgen/biome".equals(directory)) {
|
||||
// TAGS_BIOME.forEach(consumer);
|
||||
// }
|
||||
return tagsMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all {@code ids} to the {@code builder}.
|
||||
*
|
||||
* @param builder
|
||||
* @param ids
|
||||
* @return The Builder passed as {@code builder}.
|
||||
*/
|
||||
public static Tag.Builder apply(Tag.Builder builder, Set<ResourceLocation> ids) {
|
||||
ids.forEach(value -> builder.addElement(value, "BCLib Code"));
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
public static boolean isToolWithMineableTag(ItemStack stack, TagKey<Block> tag) {
|
||||
if (stack.getItem() instanceof DiggerItemAccessor dig) {
|
||||
return dig.bclib_getBlockTag().equals(tag);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package ru.bclib.api.tag;
|
||||
package org.betterx.bclib.api.tag;
|
||||
|
||||
import net.minecraft.core.DefaultedRegistry;
|
||||
import net.minecraft.core.Registry;
|
||||
|
@ -11,8 +11,8 @@ import net.minecraft.world.level.biome.Biome;
|
|||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import ru.bclib.BCLib;
|
||||
import ru.bclib.api.biomes.BiomeAPI;
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.api.biomes.BiomeAPI;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -21,7 +21,8 @@ import java.util.function.Function;
|
|||
|
||||
public class TagType<T> {
|
||||
boolean isFrozen = false;
|
||||
public static class RegistryBacked<T> extends Simple<T>{
|
||||
|
||||
public static class RegistryBacked<T> extends Simple<T> {
|
||||
private final DefaultedRegistry<T> registry;
|
||||
|
||||
RegistryBacked(DefaultedRegistry<T> registry) {
|
||||
|
@ -78,9 +79,12 @@ public class TagType<T> {
|
|||
public static class UnTyped<T> extends TagType<T> {
|
||||
UnTyped(ResourceKey<? extends Registry<T>> registry,
|
||||
String directory) {
|
||||
super(registry, directory, (t)->{throw new RuntimeException("Using Untyped TagType with Type-Dependant access. ");});
|
||||
super(registry, directory, (t) -> {
|
||||
throw new RuntimeException("Using Untyped TagType with Type-Dependant access. ");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public final String directory;
|
||||
private final Map<ResourceLocation, Set<ResourceLocation>> tags = Maps.newConcurrentMap();
|
||||
public final ResourceKey<? extends Registry<T>> registryKey;
|
||||
|
@ -142,6 +146,7 @@ public class TagType<T> {
|
|||
|
||||
/**
|
||||
* Adds one Tag to multiple Elements.
|
||||
*
|
||||
* @param tagID {@link TagKey< Biome >} tag ID.
|
||||
* @param elements array of Elements to add into tag.
|
||||
*/
|
||||
|
@ -184,7 +189,8 @@ public class TagType<T> {
|
|||
public void forEach(BiConsumer<ResourceLocation, Set<ResourceLocation>> consumer) {
|
||||
tags.forEach(consumer);
|
||||
}
|
||||
public void apply(Map<ResourceLocation, Tag.Builder> tagsMap){
|
||||
|
||||
public void apply(Map<ResourceLocation, Tag.Builder> tagsMap) {
|
||||
if (Registry.BIOME_REGISTRY.equals(registryKey)) BiomeAPI._runTagAdders();
|
||||
|
||||
//this.isFrozen = true;
|
|
@ -0,0 +1,149 @@
|
|||
package org.betterx.bclib.blockentities;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.NonNullList;
|
||||
import net.minecraft.core.Vec3i;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.sounds.SoundEvent;
|
||||
import net.minecraft.sounds.SoundEvents;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import net.minecraft.world.ContainerHelper;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
import net.minecraft.world.inventory.ChestMenu;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.BarrelBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.entity.ChestBlockEntity;
|
||||
import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
import org.betterx.bclib.blocks.BaseBarrelBlock;
|
||||
import org.betterx.bclib.registry.BaseBlockEntities;
|
||||
|
||||
public class BaseBarrelBlockEntity extends RandomizableContainerBlockEntity {
|
||||
private NonNullList<ItemStack> inventory;
|
||||
private int viewerCount;
|
||||
|
||||
private BaseBarrelBlockEntity(BlockEntityType<?> type, BlockPos blockPos, BlockState blockState) {
|
||||
super(type, blockPos, blockState);
|
||||
this.inventory = NonNullList.withSize(27, ItemStack.EMPTY);
|
||||
}
|
||||
|
||||
public BaseBarrelBlockEntity(BlockPos blockPos, BlockState blockState) {
|
||||
this(BaseBlockEntities.BARREL, blockPos, blockState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveAdditional(CompoundTag tag) {
|
||||
super.saveAdditional(tag);
|
||||
if (!this.trySaveLootTable(tag)) {
|
||||
ContainerHelper.saveAllItems(tag, this.inventory);
|
||||
}
|
||||
|
||||
//return tag;
|
||||
}
|
||||
|
||||
public void load(CompoundTag tag) {
|
||||
super.load(tag);
|
||||
this.inventory = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY);
|
||||
if (!this.tryLoadLootTable(tag)) {
|
||||
ContainerHelper.loadAllItems(tag, this.inventory);
|
||||
}
|
||||
}
|
||||
|
||||
public int getContainerSize() {
|
||||
return 27;
|
||||
}
|
||||
|
||||
protected NonNullList<ItemStack> getItems() {
|
||||
return this.inventory;
|
||||
}
|
||||
|
||||
protected void setItems(NonNullList<ItemStack> list) {
|
||||
this.inventory = list;
|
||||
}
|
||||
|
||||
protected Component getDefaultName() {
|
||||
return Component.translatable("container.barrel");
|
||||
}
|
||||
|
||||
protected AbstractContainerMenu createMenu(int syncId, Inventory playerInventory) {
|
||||
return ChestMenu.threeRows(syncId, playerInventory, this);
|
||||
}
|
||||
|
||||
public void startOpen(Player player) {
|
||||
if (!player.isSpectator()) {
|
||||
if (viewerCount < 0) {
|
||||
viewerCount = 0;
|
||||
}
|
||||
|
||||
++viewerCount;
|
||||
BlockState blockState = this.getBlockState();
|
||||
if (!blockState.getValue(BarrelBlock.OPEN)) {
|
||||
playSound(blockState, SoundEvents.BARREL_OPEN);
|
||||
setOpen(blockState, true);
|
||||
}
|
||||
|
||||
if (level != null) {
|
||||
scheduleUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleUpdate() {
|
||||
level.scheduleTick(getBlockPos(), getBlockState().getBlock(), 5);
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
if (level != null) {
|
||||
viewerCount = ChestBlockEntity.getOpenCount(level, worldPosition);
|
||||
if (viewerCount > 0) {
|
||||
scheduleUpdate();
|
||||
} else {
|
||||
BlockState blockState = getBlockState();
|
||||
if (!(blockState.getBlock() instanceof BaseBarrelBlock)) {
|
||||
setRemoved();
|
||||
return;
|
||||
}
|
||||
if (blockState.getValue(BarrelBlock.OPEN)) {
|
||||
playSound(blockState, SoundEvents.BARREL_CLOSE);
|
||||
setOpen(blockState, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void stopOpen(Player player) {
|
||||
if (!player.isSpectator()) {
|
||||
--this.viewerCount;
|
||||
}
|
||||
}
|
||||
|
||||
private void setOpen(BlockState state, boolean open) {
|
||||
if (level != null) {
|
||||
level.setBlock(this.getBlockPos(), state.setValue(BarrelBlock.OPEN, open), 3);
|
||||
}
|
||||
}
|
||||
|
||||
private void playSound(BlockState blockState, SoundEvent soundEvent) {
|
||||
if (level != null) {
|
||||
Vec3i vec3i = blockState.getValue(BarrelBlock.FACING).getNormal();
|
||||
double d = (double) this.worldPosition.getX() + 0.5D + (double) vec3i.getX() / 2.0D;
|
||||
double e = (double) this.worldPosition.getY() + 0.5D + (double) vec3i.getY() / 2.0D;
|
||||
double f = (double) this.worldPosition.getZ() + 0.5D + (double) vec3i.getZ() / 2.0D;
|
||||
level.playSound(
|
||||
null,
|
||||
d,
|
||||
e,
|
||||
f,
|
||||
soundEvent,
|
||||
SoundSource.BLOCKS,
|
||||
0.5F,
|
||||
this.level.random.nextFloat() * 0.1F + 0.9F
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package org.betterx.bclib.blockentities;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.entity.ChestBlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
import org.betterx.bclib.registry.BaseBlockEntities;
|
||||
|
||||
public class BaseChestBlockEntity extends ChestBlockEntity {
|
||||
public BaseChestBlockEntity(BlockPos blockPos, BlockState blockState) {
|
||||
super(BaseBlockEntities.CHEST, blockPos, blockState);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package org.betterx.bclib.blockentities;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
import net.minecraft.world.inventory.FurnaceMenu;
|
||||
import net.minecraft.world.item.crafting.RecipeType;
|
||||
import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
import org.betterx.bclib.registry.BaseBlockEntities;
|
||||
|
||||
public class BaseFurnaceBlockEntity extends AbstractFurnaceBlockEntity {
|
||||
public BaseFurnaceBlockEntity(BlockPos blockPos, BlockState blockState) {
|
||||
super(BaseBlockEntities.FURNACE, blockPos, blockState, RecipeType.SMELTING);
|
||||
}
|
||||
|
||||
protected Component getDefaultName() {
|
||||
return Component.translatable("container.furnace");
|
||||
}
|
||||
|
||||
protected AbstractContainerMenu createMenu(int syncId, Inventory playerInventory) {
|
||||
return new FurnaceMenu(syncId, playerInventory, this, this.dataAccess);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package org.betterx.bclib.blockentities;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.entity.SignBlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
import org.betterx.bclib.registry.BaseBlockEntities;
|
||||
|
||||
public class BaseSignBlockEntity extends SignBlockEntity {
|
||||
public BaseSignBlockEntity(BlockPos blockPos, BlockState blockState) {
|
||||
super(blockPos, blockState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockEntityType<?> getType() {
|
||||
return BaseBlockEntities.SIGN;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package org.betterx.bclib.blockentities;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class DynamicBlockEntityType<T extends BlockEntity> extends BlockEntityType<T> {
|
||||
|
||||
private final Set<Block> validBlocks = Sets.newHashSet();
|
||||
private final BlockEntitySupplier<? extends T> factory;
|
||||
|
||||
public DynamicBlockEntityType(BlockEntitySupplier<? extends T> supplier) {
|
||||
super(null, Collections.emptySet(), null);
|
||||
this.factory = supplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public T create(BlockPos blockPos, BlockState blockState) {
|
||||
return factory.create(blockPos, blockState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(BlockState blockState) {
|
||||
return validBlocks.contains(blockState.getBlock());
|
||||
}
|
||||
|
||||
public void registerBlock(Block block) {
|
||||
validBlocks.add(block);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface BlockEntitySupplier<T extends BlockEntity> {
|
||||
T create(BlockPos blockPos, BlockState blockState);
|
||||
}
|
||||
}
|
138
src/main/java/org/betterx/bclib/blocks/BaseAnvilBlock.java
Normal file
138
src/main/java/org/betterx/bclib/blocks/BaseAnvilBlock.java
Normal file
|
@ -0,0 +1,138 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.client.resources.model.UnbakedModel;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.item.BlockItem;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.PickaxeItem;
|
||||
import net.minecraft.world.level.block.AnvilBlock;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.StateDefinition;
|
||||
import net.minecraft.world.level.block.state.properties.IntegerProperty;
|
||||
import net.minecraft.world.level.material.MaterialColor;
|
||||
import net.minecraft.world.level.storage.loot.LootContext;
|
||||
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.item.v1.FabricItemSettings;
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import org.betterx.bclib.client.models.BasePatterns;
|
||||
import org.betterx.bclib.client.models.ModelsHelper;
|
||||
import org.betterx.bclib.client.models.PatternsHelper;
|
||||
import org.betterx.bclib.interfaces.BlockModelProvider;
|
||||
import org.betterx.bclib.interfaces.CustomItemProvider;
|
||||
import org.betterx.bclib.items.BaseAnvilItem;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public abstract class BaseAnvilBlock extends AnvilBlock implements BlockModelProvider, CustomItemProvider {
|
||||
public static final IntegerProperty DESTRUCTION = BlockProperties.DESTRUCTION;
|
||||
public IntegerProperty durability;
|
||||
|
||||
public BaseAnvilBlock(MaterialColor color) {
|
||||
this(FabricBlockSettings.copyOf(Blocks.ANVIL).color(color));
|
||||
}
|
||||
|
||||
public BaseAnvilBlock(BlockBehaviour.Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
|
||||
super.createBlockStateDefinition(builder);
|
||||
if (getMaxDurability() != 3) {
|
||||
durability = IntegerProperty.create("durability", 0, getMaxDurability());
|
||||
} else {
|
||||
durability = BlockProperties.DEFAULT_ANVIL_DURABILITY;
|
||||
}
|
||||
builder.add(DESTRUCTION, durability);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public BlockModel getItemModel(ResourceLocation blockId) {
|
||||
return getBlockModel(blockId, defaultBlockState());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) {
|
||||
int destruction = blockState.getValue(DESTRUCTION);
|
||||
String name = blockId.getPath();
|
||||
Map<String, String> textures = Maps.newHashMap();
|
||||
textures.put("%modid%", blockId.getNamespace());
|
||||
textures.put("%anvil%", name);
|
||||
textures.put("%top%", name + "_top_" + destruction);
|
||||
Optional<String> pattern = PatternsHelper.createJson(BasePatterns.BLOCK_ANVIL, textures);
|
||||
return ModelsHelper.fromPattern(pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public UnbakedModel getModelVariant(ResourceLocation stateId,
|
||||
BlockState blockState,
|
||||
Map<ResourceLocation, UnbakedModel> modelCache) {
|
||||
int destruction = blockState.getValue(DESTRUCTION);
|
||||
String modId = stateId.getNamespace();
|
||||
String modelId = "block/" + stateId.getPath() + "_top_" + destruction;
|
||||
ResourceLocation modelLocation = new ResourceLocation(modId, modelId);
|
||||
registerBlockModel(stateId, modelLocation, blockState, modelCache);
|
||||
return ModelsHelper.createFacingModel(modelLocation, blockState.getValue(FACING), false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockItem getCustomItem(ResourceLocation blockID, FabricItemSettings settings) {
|
||||
return new BaseAnvilItem(this, settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
|
||||
int destruction = state.getValue(DESTRUCTION);
|
||||
int durability = state.getValue(getDurabilityProp());
|
||||
int value = destruction * getMaxDurability() + durability;
|
||||
ItemStack tool = builder.getParameter(LootContextParams.TOOL);
|
||||
if (tool != null && tool.getItem() instanceof PickaxeItem) {
|
||||
ItemStack itemStack = new ItemStack(this);
|
||||
itemStack.getOrCreateTag().putInt(BaseAnvilItem.DESTRUCTION, value);
|
||||
return Lists.newArrayList(itemStack);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public IntegerProperty getDurabilityProp() {
|
||||
return durability;
|
||||
}
|
||||
|
||||
public int getMaxDurability() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
public BlockState damageAnvilUse(BlockState state, RandomSource random) {
|
||||
IntegerProperty durability = getDurabilityProp();
|
||||
int value = state.getValue(durability);
|
||||
if (value < getMaxDurability() && random.nextInt(8) == 0) {
|
||||
return state.setValue(durability, value + 1);
|
||||
}
|
||||
value = state.getValue(DESTRUCTION);
|
||||
return value < 2 ? state.setValue(DESTRUCTION, value + 1).setValue(durability, 0) : null;
|
||||
}
|
||||
|
||||
public BlockState damageAnvilFall(BlockState state) {
|
||||
int destruction = state.getValue(DESTRUCTION);
|
||||
return destruction < 2 ? state.setValue(DESTRUCTION, destruction + 1) : null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.tags.BlockTags;
|
||||
import net.minecraft.world.item.context.BlockPlaceContext;
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
import net.minecraft.world.level.LevelReader;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.Mirror;
|
||||
import net.minecraft.world.level.block.Rotation;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.StateDefinition;
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
||||
import net.minecraft.world.level.block.state.properties.DirectionProperty;
|
||||
|
||||
import org.betterx.bclib.util.BlocksHelper;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public abstract class BaseAttachedBlock extends BaseBlockNotFull {
|
||||
public static final DirectionProperty FACING = BlockStateProperties.FACING;
|
||||
|
||||
public BaseAttachedBlock(Properties settings) {
|
||||
super(settings);
|
||||
registerDefaultState(defaultBlockState().setValue(FACING, Direction.UP));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> stateManager) {
|
||||
stateManager.add(FACING);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getStateForPlacement(BlockPlaceContext ctx) {
|
||||
BlockState blockState = defaultBlockState();
|
||||
LevelReader worldView = ctx.getLevel();
|
||||
BlockPos blockPos = ctx.getClickedPos();
|
||||
Direction[] directions = ctx.getNearestLookingDirections();
|
||||
for (Direction direction : directions) {
|
||||
Direction direction2 = direction.getOpposite();
|
||||
blockState = blockState.setValue(FACING, direction2);
|
||||
if (blockState.canSurvive(worldView, blockPos)) {
|
||||
return blockState;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) {
|
||||
Direction direction = state.getValue(FACING);
|
||||
BlockPos blockPos = pos.relative(direction.getOpposite());
|
||||
return canSupportCenter(world, blockPos, direction) || world.getBlockState(blockPos).is(BlockTags.LEAVES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState updateShape(BlockState state,
|
||||
Direction facing,
|
||||
BlockState neighborState,
|
||||
LevelAccessor world,
|
||||
BlockPos pos,
|
||||
BlockPos neighborPos) {
|
||||
if (!canSurvive(state, world, pos)) {
|
||||
return Blocks.AIR.defaultBlockState();
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public BlockState rotate(BlockState state, Rotation rotation) {
|
||||
return BlocksHelper.rotateHorizontal(state, rotation, FACING);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState mirror(BlockState state, Mirror mirror) {
|
||||
return BlocksHelper.mirrorHorizontal(state, mirror, FACING);
|
||||
}
|
||||
}
|
26
src/main/java/org/betterx/bclib/blocks/BaseBarkBlock.java
Normal file
26
src/main/java/org/betterx/bclib/blocks/BaseBarkBlock.java
Normal file
|
@ -0,0 +1,26 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import org.betterx.bclib.client.models.BasePatterns;
|
||||
import org.betterx.bclib.client.models.PatternsHelper;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class BaseBarkBlock extends BaseRotatedPillarBlock {
|
||||
public BaseBarkBlock(Properties settings) {
|
||||
super(settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<String> createBlockPattern(ResourceLocation blockId) {
|
||||
blockId = Registry.BLOCK.getKey(this);
|
||||
return PatternsHelper.createJson(BasePatterns.BLOCK_BASE, replacePath(blockId));
|
||||
}
|
||||
|
||||
private ResourceLocation replacePath(ResourceLocation blockId) {
|
||||
String newPath = blockId.getPath().replace("_bark", "_log_side");
|
||||
return new ResourceLocation(blockId.getNamespace(), newPath);
|
||||
}
|
||||
}
|
159
src/main/java/org/betterx/bclib/blocks/BaseBarrelBlock.java
Normal file
159
src/main/java/org/betterx/bclib/blocks/BaseBarrelBlock.java
Normal file
|
@ -0,0 +1,159 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.client.resources.model.BlockModelRotation;
|
||||
import net.minecraft.client.resources.model.UnbakedModel;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.stats.Stats;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.monster.piglin.PiglinAi;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.BarrelBlock;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.RenderShape;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.storage.loot.LootContext;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
|
||||
import org.betterx.bclib.blockentities.BaseBarrelBlockEntity;
|
||||
import org.betterx.bclib.client.models.BasePatterns;
|
||||
import org.betterx.bclib.client.models.ModelsHelper;
|
||||
import org.betterx.bclib.client.models.PatternsHelper;
|
||||
import org.betterx.bclib.interfaces.BlockModelProvider;
|
||||
import org.betterx.bclib.registry.BaseBlockEntities;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class BaseBarrelBlock extends BarrelBlock implements BlockModelProvider {
|
||||
public BaseBarrelBlock(Block source) {
|
||||
this(FabricBlockSettings.copyOf(source).noOcclusion());
|
||||
}
|
||||
|
||||
public BaseBarrelBlock(BlockBehaviour.Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) {
|
||||
return BaseBlockEntities.BARREL.create(blockPos, blockState);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
|
||||
List<ItemStack> drop = super.getDrops(state, builder);
|
||||
drop.add(new ItemStack(this.asItem()));
|
||||
return drop;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResult use(BlockState state,
|
||||
Level world,
|
||||
BlockPos pos,
|
||||
Player player,
|
||||
InteractionHand hand,
|
||||
BlockHitResult hit) {
|
||||
if (world.isClientSide) {
|
||||
return InteractionResult.SUCCESS;
|
||||
} else {
|
||||
BlockEntity blockEntity = world.getBlockEntity(pos);
|
||||
if (blockEntity instanceof BaseBarrelBlockEntity) {
|
||||
player.openMenu((BaseBarrelBlockEntity) blockEntity);
|
||||
player.awardStat(Stats.OPEN_BARREL);
|
||||
PiglinAi.angerNearbyPiglins(player, true);
|
||||
}
|
||||
|
||||
return InteractionResult.CONSUME;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
|
||||
BlockEntity blockEntity = world.getBlockEntity(pos);
|
||||
if (blockEntity instanceof BaseBarrelBlockEntity) {
|
||||
((BaseBarrelBlockEntity) blockEntity).tick();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RenderShape getRenderShape(BlockState state) {
|
||||
return RenderShape.MODEL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlacedBy(Level world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack itemStack) {
|
||||
if (itemStack.hasCustomHoverName()) {
|
||||
BlockEntity blockEntity = world.getBlockEntity(pos);
|
||||
if (blockEntity instanceof BaseBarrelBlockEntity) {
|
||||
((BaseBarrelBlockEntity) blockEntity).setCustomName(itemStack.getHoverName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public BlockModel getItemModel(ResourceLocation blockId) {
|
||||
return getBlockModel(blockId, defaultBlockState());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) {
|
||||
Optional<String> pattern;
|
||||
if (blockState.getValue(OPEN)) {
|
||||
pattern = PatternsHelper.createJson(BasePatterns.BLOCK_BARREL_OPEN, blockId);
|
||||
} else {
|
||||
pattern = PatternsHelper.createJson(BasePatterns.BLOCK_BOTTOM_TOP, blockId);
|
||||
}
|
||||
return ModelsHelper.fromPattern(pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public UnbakedModel getModelVariant(ResourceLocation stateId,
|
||||
BlockState blockState,
|
||||
Map<ResourceLocation, UnbakedModel> modelCache) {
|
||||
String open = blockState.getValue(OPEN) ? "_open" : "";
|
||||
ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + open);
|
||||
registerBlockModel(stateId, modelId, blockState, modelCache);
|
||||
Direction facing = blockState.getValue(FACING);
|
||||
BlockModelRotation rotation = BlockModelRotation.X0_Y0;
|
||||
switch (facing) {
|
||||
case NORTH:
|
||||
rotation = BlockModelRotation.X90_Y0;
|
||||
break;
|
||||
case EAST:
|
||||
rotation = BlockModelRotation.X90_Y90;
|
||||
break;
|
||||
case SOUTH:
|
||||
rotation = BlockModelRotation.X90_Y180;
|
||||
break;
|
||||
case WEST:
|
||||
rotation = BlockModelRotation.X90_Y270;
|
||||
break;
|
||||
case DOWN:
|
||||
rotation = BlockModelRotation.X180_Y0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return ModelsHelper.createMultiVariant(modelId, rotation.getRotation(), false);
|
||||
}
|
||||
}
|
76
src/main/java/org/betterx/bclib/blocks/BaseBlock.java
Normal file
76
src/main/java/org/betterx/bclib/blocks/BaseBlock.java
Normal file
|
@ -0,0 +1,76 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.material.MaterialColor;
|
||||
import net.minecraft.world.level.storage.loot.LootContext;
|
||||
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
|
||||
import org.betterx.bclib.interfaces.BlockModelProvider;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Base class for a default Block.
|
||||
* <p>
|
||||
* This Block-Type will:
|
||||
* <ul>
|
||||
* <li>Drop itself</li>
|
||||
* <li>Automatically create an Item-Model from the Block-Model</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class BaseBlock extends Block implements BlockModelProvider {
|
||||
/**
|
||||
* Creates a new Block with the passed properties
|
||||
*
|
||||
* @param settings The properties of the Block.
|
||||
*/
|
||||
public BaseBlock(Properties settings) {
|
||||
super(settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>
|
||||
* This implementation will drop the Block itself
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
|
||||
return Collections.singletonList(new ItemStack(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>
|
||||
* This implementation will load the Block-Model and return it as the Item-Model
|
||||
*/
|
||||
@Override
|
||||
public BlockModel getItemModel(ResourceLocation blockId) {
|
||||
return getBlockModel(blockId, defaultBlockState());
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used internally.
|
||||
* <p>
|
||||
* It is called from Block-Contructors, to allow the augmentation of the blocks
|
||||
* preset properties.
|
||||
* <p>
|
||||
* For example in {@link BaseLeavesBlock#BaseLeavesBlock(Block, MaterialColor, Consumer)}
|
||||
*
|
||||
* @param customizeProperties A {@link Consumer} to call with the preset properties
|
||||
* @param settings The properties as created by the Block
|
||||
* @return The reconfigured {@code settings}
|
||||
*/
|
||||
static FabricBlockSettings acceptAndReturn(Consumer<FabricBlockSettings> customizeProperties,
|
||||
FabricBlockSettings settings) {
|
||||
customizeProperties.accept(settings);
|
||||
return settings;
|
||||
}
|
||||
}
|
24
src/main/java/org/betterx/bclib/blocks/BaseBlockNotFull.java
Normal file
24
src/main/java/org/betterx/bclib/blocks/BaseBlockNotFull.java
Normal file
|
@ -0,0 +1,24 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
public class BaseBlockNotFull extends BaseBlock {
|
||||
public BaseBlockNotFull(Properties settings) {
|
||||
super(settings);
|
||||
}
|
||||
|
||||
public boolean canSuffocate(BlockState state, BlockGetter view, BlockPos pos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isSimpleFullBlock(BlockState state, BlockGetter view, BlockPos pos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean allowsSpawning(BlockState state, BlockGetter view, BlockPos pos, EntityType<?> type) {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.BaseEntityBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.storage.loot.LootContext;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class BaseBlockWithEntity extends BaseEntityBlock {
|
||||
public BaseBlockWithEntity(Properties settings) {
|
||||
super(settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
|
||||
return Collections.singletonList(new ItemStack(this));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.item.enchantment.EnchantmentHelper;
|
||||
import net.minecraft.world.item.enchantment.Enchantments;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.storage.loot.LootContext;
|
||||
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
|
||||
import org.betterx.bclib.client.models.BasePatterns;
|
||||
import org.betterx.bclib.client.models.ModelsHelper;
|
||||
import org.betterx.bclib.client.models.PatternsHelper;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class BaseBookshelfBlock extends BaseBlock {
|
||||
public BaseBookshelfBlock(Block source) {
|
||||
this(FabricBlockSettings.copyOf(source));
|
||||
}
|
||||
|
||||
public BaseBookshelfBlock(BlockBehaviour.Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
|
||||
ItemStack tool = builder.getParameter(LootContextParams.TOOL);
|
||||
if (tool != null) {
|
||||
int silk = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool);
|
||||
if (silk > 0) {
|
||||
return Collections.singletonList(new ItemStack(this));
|
||||
}
|
||||
}
|
||||
return Collections.singletonList(new ItemStack(Items.BOOK, 3));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) {
|
||||
Optional<String> pattern = PatternsHelper.createJson(BasePatterns.BLOCK_BOOKSHELF, replacePath(blockId));
|
||||
return ModelsHelper.fromPattern(pattern);
|
||||
}
|
||||
|
||||
private ResourceLocation replacePath(ResourceLocation blockId) {
|
||||
String newPath = blockId.getPath().replace("_bookshelf", "");
|
||||
return new ResourceLocation(blockId.getNamespace(), newPath);
|
||||
}
|
||||
}
|
108
src/main/java/org/betterx/bclib/blocks/BaseButtonBlock.java
Normal file
108
src/main/java/org/betterx/bclib/blocks/BaseButtonBlock.java
Normal file
|
@ -0,0 +1,108 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.client.resources.model.BlockModelRotation;
|
||||
import net.minecraft.client.resources.model.UnbakedModel;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.ButtonBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.properties.AttachFace;
|
||||
import net.minecraft.world.level.storage.loot.LootContext;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
|
||||
import org.betterx.bclib.client.models.BasePatterns;
|
||||
import org.betterx.bclib.client.models.ModelsHelper;
|
||||
import org.betterx.bclib.client.models.PatternsHelper;
|
||||
import org.betterx.bclib.interfaces.BlockModelProvider;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public abstract class BaseButtonBlock extends ButtonBlock implements BlockModelProvider {
|
||||
private final Block parent;
|
||||
|
||||
protected BaseButtonBlock(Block parent, Properties properties, boolean sensitive) {
|
||||
super(sensitive, properties);
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
|
||||
return Collections.singletonList(new ItemStack(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public BlockModel getItemModel(ResourceLocation blockId) {
|
||||
ResourceLocation parentId = Registry.BLOCK.getKey(parent);
|
||||
Optional<String> pattern = PatternsHelper.createJson(BasePatterns.ITEM_BUTTON, parentId);
|
||||
return ModelsHelper.fromPattern(pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) {
|
||||
ResourceLocation parentId = Registry.BLOCK.getKey(parent);
|
||||
Optional<String> pattern = blockState.getValue(POWERED)
|
||||
? PatternsHelper.createJson(
|
||||
BasePatterns.BLOCK_BUTTON_PRESSED,
|
||||
parentId
|
||||
)
|
||||
: PatternsHelper.createJson(BasePatterns.BLOCK_BUTTON, parentId);
|
||||
return ModelsHelper.fromPattern(pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public UnbakedModel getModelVariant(ResourceLocation stateId,
|
||||
BlockState blockState,
|
||||
Map<ResourceLocation, UnbakedModel> modelCache) {
|
||||
String powered = blockState.getValue(POWERED) ? "_powered" : "";
|
||||
ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + powered);
|
||||
registerBlockModel(stateId, modelId, blockState, modelCache);
|
||||
AttachFace face = blockState.getValue(FACE);
|
||||
boolean isCeiling = face == AttachFace.CEILING;
|
||||
int x = 0, y = 0;
|
||||
switch (face) {
|
||||
case CEILING:
|
||||
x = 180;
|
||||
break;
|
||||
case WALL:
|
||||
x = 90;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
switch (blockState.getValue(FACING)) {
|
||||
case NORTH:
|
||||
if (isCeiling) {
|
||||
y = 180;
|
||||
}
|
||||
break;
|
||||
case EAST:
|
||||
y = isCeiling ? 270 : 90;
|
||||
break;
|
||||
case SOUTH:
|
||||
if (!isCeiling) {
|
||||
y = 180;
|
||||
}
|
||||
break;
|
||||
case WEST:
|
||||
y = isCeiling ? 90 : 270;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
BlockModelRotation rotation = BlockModelRotation.by(x, y);
|
||||
return ModelsHelper.createMultiVariant(modelId, rotation.getRotation(), face == AttachFace.WALL);
|
||||
}
|
||||
}
|
75
src/main/java/org/betterx/bclib/blocks/BaseChainBlock.java
Normal file
75
src/main/java/org/betterx/bclib/blocks/BaseChainBlock.java
Normal file
|
@ -0,0 +1,75 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.client.resources.model.UnbakedModel;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.ChainBlock;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.material.MaterialColor;
|
||||
import net.minecraft.world.level.storage.loot.LootContext;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
|
||||
import org.betterx.bclib.client.models.BasePatterns;
|
||||
import org.betterx.bclib.client.models.ModelsHelper;
|
||||
import org.betterx.bclib.client.models.PatternsHelper;
|
||||
import org.betterx.bclib.client.render.BCLRenderLayer;
|
||||
import org.betterx.bclib.interfaces.BlockModelProvider;
|
||||
import org.betterx.bclib.interfaces.RenderLayerProvider;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class BaseChainBlock extends ChainBlock implements BlockModelProvider, RenderLayerProvider {
|
||||
public BaseChainBlock(MaterialColor color) {
|
||||
this(FabricBlockSettings.copyOf(Blocks.CHAIN).color(color));
|
||||
}
|
||||
|
||||
public BaseChainBlock(BlockBehaviour.Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
|
||||
return Collections.singletonList(new ItemStack(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public BlockModel getItemModel(ResourceLocation blockId) {
|
||||
return ModelsHelper.createItemModel(blockId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) {
|
||||
Optional<String> pattern = PatternsHelper.createJson(BasePatterns.BLOCK_CHAIN, blockId);
|
||||
return ModelsHelper.fromPattern(pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public UnbakedModel getModelVariant(ResourceLocation stateId,
|
||||
BlockState blockState,
|
||||
Map<ResourceLocation, UnbakedModel> modelCache) {
|
||||
Direction.Axis axis = blockState.getValue(AXIS);
|
||||
ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath());
|
||||
registerBlockModel(stateId, modelId, blockState, modelCache);
|
||||
return ModelsHelper.createRotatedModel(modelId, axis);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BCLRenderLayer getRenderLayer() {
|
||||
return BCLRenderLayer.CUTOUT;
|
||||
}
|
||||
}
|
62
src/main/java/org/betterx/bclib/blocks/BaseChestBlock.java
Normal file
62
src/main/java/org/betterx/bclib/blocks/BaseChestBlock.java
Normal file
|
@ -0,0 +1,62 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.ChestBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.storage.loot.LootContext;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
|
||||
import org.betterx.bclib.client.models.BasePatterns;
|
||||
import org.betterx.bclib.client.models.ModelsHelper;
|
||||
import org.betterx.bclib.client.models.PatternsHelper;
|
||||
import org.betterx.bclib.interfaces.BlockModelProvider;
|
||||
import org.betterx.bclib.registry.BaseBlockEntities;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class BaseChestBlock extends ChestBlock implements BlockModelProvider {
|
||||
private final Block parent;
|
||||
|
||||
public BaseChestBlock(Block source) {
|
||||
super(FabricBlockSettings.copyOf(source).noOcclusion(), () -> BaseBlockEntities.CHEST);
|
||||
this.parent = source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) {
|
||||
return BaseBlockEntities.CHEST.create(blockPos, blockState);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
|
||||
List<ItemStack> drop = super.getDrops(state, builder);
|
||||
drop.add(new ItemStack(this.asItem()));
|
||||
return drop;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public BlockModel getItemModel(ResourceLocation blockId) {
|
||||
Optional<String> pattern = PatternsHelper.createJson(BasePatterns.ITEM_CHEST, blockId);
|
||||
return ModelsHelper.fromPattern(pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) {
|
||||
ResourceLocation parentId = Registry.BLOCK.getKey(parent);
|
||||
return ModelsHelper.createBlockEmpty(parentId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.client.resources.model.UnbakedModel;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.ComposterBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.storage.loot.LootContext;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
|
||||
import org.betterx.bclib.client.models.BasePatterns;
|
||||
import org.betterx.bclib.client.models.ModelsHelper;
|
||||
import org.betterx.bclib.client.models.PatternsHelper;
|
||||
import org.betterx.bclib.interfaces.BlockModelProvider;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class BaseComposterBlock extends ComposterBlock implements BlockModelProvider {
|
||||
public BaseComposterBlock(Block source) {
|
||||
super(FabricBlockSettings.copyOf(source));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
|
||||
return Collections.singletonList(new ItemStack(this.asItem()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public BlockModel getItemModel(ResourceLocation resourceLocation) {
|
||||
return getBlockModel(resourceLocation, defaultBlockState());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) {
|
||||
Optional<String> pattern = PatternsHelper.createJson(BasePatterns.BLOCK_COMPOSTER, blockId);
|
||||
return ModelsHelper.fromPattern(pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public UnbakedModel getModelVariant(ResourceLocation stateId,
|
||||
BlockState blockState,
|
||||
Map<ResourceLocation, UnbakedModel> modelCache) {
|
||||
ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath());
|
||||
registerBlockModel(stateId, modelId, blockState, modelCache);
|
||||
|
||||
ModelsHelper.MultiPartBuilder builder = ModelsHelper.MultiPartBuilder.create(stateDefinition);
|
||||
LEVEL.getPossibleValues().forEach(level -> {
|
||||
if (level > 0) {
|
||||
ResourceLocation contentId;
|
||||
if (level > 7) {
|
||||
contentId = new ResourceLocation("block/composter_contents_ready");
|
||||
} else {
|
||||
contentId = new ResourceLocation("block/composter_contents" + level);
|
||||
}
|
||||
builder.part(contentId).setCondition(state -> state.getValue(LEVEL).equals(level)).add();
|
||||
}
|
||||
});
|
||||
builder.part(modelId).add();
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.CraftingTableBlock;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.storage.loot.LootContext;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
|
||||
import org.betterx.bclib.client.models.BasePatterns;
|
||||
import org.betterx.bclib.client.models.ModelsHelper;
|
||||
import org.betterx.bclib.client.models.PatternsHelper;
|
||||
import org.betterx.bclib.interfaces.BlockModelProvider;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class BaseCraftingTableBlock extends CraftingTableBlock implements BlockModelProvider {
|
||||
public BaseCraftingTableBlock(Block source) {
|
||||
this(FabricBlockSettings.copyOf(source));
|
||||
}
|
||||
|
||||
public BaseCraftingTableBlock(BlockBehaviour.Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
|
||||
return Collections.singletonList(new ItemStack(this.asItem()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public BlockModel getItemModel(ResourceLocation resourceLocation) {
|
||||
return getBlockModel(resourceLocation, defaultBlockState());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) {
|
||||
String blockName = blockId.getPath();
|
||||
Optional<String> pattern = PatternsHelper.createJson(BasePatterns.BLOCK_SIDED, new HashMap<String, String>() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
{
|
||||
put("%modid%", blockId.getNamespace());
|
||||
put("%particle%", blockName + "_front");
|
||||
put("%down%", blockName + "_bottom");
|
||||
put("%up%", blockName + "_top");
|
||||
put("%north%", blockName + "_front");
|
||||
put("%south%", blockName + "_side");
|
||||
put("%west%", blockName + "_front");
|
||||
put("%east%", blockName + "_side");
|
||||
}
|
||||
});
|
||||
return ModelsHelper.fromPattern(pattern);
|
||||
}
|
||||
}
|
124
src/main/java/org/betterx/bclib/blocks/BaseCropBlock.java
Normal file
124
src/main/java/org/betterx/bclib/blocks/BaseCropBlock.java
Normal file
|
@ -0,0 +1,124 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.enchantment.EnchantmentHelper;
|
||||
import net.minecraft.world.item.enchantment.Enchantments;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.SoundType;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.StateDefinition;
|
||||
import net.minecraft.world.level.block.state.properties.IntegerProperty;
|
||||
import net.minecraft.world.level.material.Material;
|
||||
import net.minecraft.world.level.storage.loot.LootContext;
|
||||
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
|
||||
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import org.betterx.bclib.util.BlocksHelper;
|
||||
import org.betterx.bclib.util.MHelper;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class BaseCropBlock extends BasePlantBlock {
|
||||
public static final IntegerProperty AGE = IntegerProperty.create("age", 0, 3);
|
||||
private static final VoxelShape SHAPE = box(2, 0, 2, 14, 14, 14);
|
||||
|
||||
private final Block[] terrain;
|
||||
private final Item drop;
|
||||
|
||||
public BaseCropBlock(Item drop, Block... terrain) {
|
||||
this(
|
||||
FabricBlockSettings.of(Material.PLANT)
|
||||
.sound(SoundType.GRASS)
|
||||
.randomTicks()
|
||||
.noCollission()
|
||||
.offsetType(BlockBehaviour.OffsetType.NONE),
|
||||
drop, terrain
|
||||
);
|
||||
}
|
||||
|
||||
public BaseCropBlock(BlockBehaviour.Properties properties, Item drop, Block... terrain) {
|
||||
super(properties.offsetType(BlockBehaviour.OffsetType.NONE));
|
||||
this.drop = drop;
|
||||
this.terrain = terrain;
|
||||
this.registerDefaultState(defaultBlockState().setValue(AGE, 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> stateManager) {
|
||||
stateManager.add(AGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isTerrain(BlockState state) {
|
||||
for (Block block : terrain) {
|
||||
if (state.is(block)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
|
||||
if (state.getValue(AGE) < 3) {
|
||||
return Collections.singletonList(new ItemStack(this));
|
||||
}
|
||||
ItemStack tool = builder.getParameter(LootContextParams.TOOL);
|
||||
if (tool != null && tool.isCorrectToolForDrops(state)) {
|
||||
int enchantment = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.BLOCK_FORTUNE, tool);
|
||||
if (enchantment > 0) {
|
||||
int countSeeds = MHelper.randRange(Mth.clamp(1 + enchantment, 1, 3), 3, MHelper.RANDOM_SOURCE);
|
||||
int countDrops = MHelper.randRange(Mth.clamp(1 + enchantment, 1, 2), 2, MHelper.RANDOM_SOURCE);
|
||||
return Lists.newArrayList(new ItemStack(this, countSeeds), new ItemStack(drop, countDrops));
|
||||
}
|
||||
}
|
||||
int countSeeds = MHelper.randRange(1, 3, MHelper.RANDOM_SOURCE);
|
||||
int countDrops = MHelper.randRange(1, 2, MHelper.RANDOM_SOURCE);
|
||||
return Lists.newArrayList(new ItemStack(this, countSeeds), new ItemStack(drop, countDrops));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
|
||||
int age = state.getValue(AGE);
|
||||
if (age < 3) {
|
||||
BlocksHelper.setWithUpdate(level, pos, state.setValue(AGE, age + 1));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidBonemealTarget(BlockGetter world, BlockPos pos, BlockState state, boolean isClient) {
|
||||
return state.getValue(AGE) < 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBonemealSuccess(Level level, RandomSource random, BlockPos pos, BlockState state) {
|
||||
return state.getValue(AGE) < 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
|
||||
super.tick(state, world, pos, random);
|
||||
if (isBonemealSuccess(world, random, pos, state) && random.nextInt(8) == 0) {
|
||||
performBonemeal(world, random, pos, state);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) {
|
||||
return SHAPE;
|
||||
}
|
||||
}
|
181
src/main/java/org/betterx/bclib/blocks/BaseDoorBlock.java
Normal file
181
src/main/java/org/betterx/bclib/blocks/BaseDoorBlock.java
Normal file
|
@ -0,0 +1,181 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.client.resources.model.BlockModelRotation;
|
||||
import net.minecraft.client.resources.model.UnbakedModel;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.util.StringRepresentable;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.DoorBlock;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.properties.DoorHingeSide;
|
||||
import net.minecraft.world.level.block.state.properties.DoubleBlockHalf;
|
||||
import net.minecraft.world.level.storage.loot.LootContext;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
|
||||
import org.betterx.bclib.api.tag.NamedBlockTags;
|
||||
import org.betterx.bclib.api.tag.NamedItemTags;
|
||||
import org.betterx.bclib.client.models.BasePatterns;
|
||||
import org.betterx.bclib.client.models.ModelsHelper;
|
||||
import org.betterx.bclib.client.models.PatternsHelper;
|
||||
import org.betterx.bclib.client.render.BCLRenderLayer;
|
||||
import org.betterx.bclib.interfaces.BlockModelProvider;
|
||||
import org.betterx.bclib.interfaces.RenderLayerProvider;
|
||||
import org.betterx.bclib.interfaces.TagProvider;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class BaseDoorBlock extends DoorBlock implements RenderLayerProvider, BlockModelProvider, TagProvider {
|
||||
public BaseDoorBlock(Block source) {
|
||||
this(FabricBlockSettings.copyOf(source).strength(3F, 3F).noOcclusion());
|
||||
}
|
||||
|
||||
public BaseDoorBlock(BlockBehaviour.Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
|
||||
if (state.getValue(HALF) == DoubleBlockHalf.LOWER)
|
||||
return Collections.singletonList(new ItemStack(this.asItem()));
|
||||
else return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BCLRenderLayer getRenderLayer() {
|
||||
return BCLRenderLayer.CUTOUT;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) {
|
||||
DoorType doorType = getDoorType(blockState);
|
||||
Optional<String> pattern = PatternsHelper.createJson(BasePatterns.BLOCK_DOOR_BOTTOM, resourceLocation);
|
||||
switch (doorType) {
|
||||
case TOP_HINGE:
|
||||
pattern = PatternsHelper.createJson(BasePatterns.BLOCK_DOOR_TOP_HINGE, resourceLocation);
|
||||
break;
|
||||
case BOTTOM_HINGE:
|
||||
pattern = PatternsHelper.createJson(BasePatterns.BLOCK_DOOR_BOTTOM_HINGE, resourceLocation);
|
||||
break;
|
||||
case TOP:
|
||||
pattern = PatternsHelper.createJson(BasePatterns.BLOCK_DOOR_TOP, resourceLocation);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return ModelsHelper.fromPattern(pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public UnbakedModel getModelVariant(ResourceLocation stateId,
|
||||
BlockState blockState,
|
||||
Map<ResourceLocation, UnbakedModel> modelCache) {
|
||||
Direction facing = blockState.getValue(FACING);
|
||||
DoorType doorType = getDoorType(blockState);
|
||||
boolean open = blockState.getValue(OPEN);
|
||||
boolean hinge = doorType.isHinge();
|
||||
BlockModelRotation rotation = BlockModelRotation.X0_Y0;
|
||||
switch (facing) {
|
||||
case EAST:
|
||||
if (hinge && open) {
|
||||
rotation = BlockModelRotation.X0_Y90;
|
||||
} else if (open) {
|
||||
rotation = BlockModelRotation.X0_Y270;
|
||||
}
|
||||
break;
|
||||
case SOUTH:
|
||||
if (!hinge && !open || hinge && !open) {
|
||||
rotation = BlockModelRotation.X0_Y90;
|
||||
} else if (hinge) {
|
||||
rotation = BlockModelRotation.X0_Y180;
|
||||
}
|
||||
break;
|
||||
case WEST:
|
||||
if (!hinge && !open || hinge && !open) {
|
||||
rotation = BlockModelRotation.X0_Y180;
|
||||
} else if (hinge) {
|
||||
rotation = BlockModelRotation.X0_Y270;
|
||||
} else {
|
||||
rotation = BlockModelRotation.X0_Y90;
|
||||
}
|
||||
break;
|
||||
case NORTH:
|
||||
default:
|
||||
if (!hinge && !open || hinge && !open) {
|
||||
rotation = BlockModelRotation.X0_Y270;
|
||||
} else if (!hinge) {
|
||||
rotation = BlockModelRotation.X0_Y180;
|
||||
}
|
||||
break;
|
||||
}
|
||||
ResourceLocation modelId = new ResourceLocation(
|
||||
stateId.getNamespace(),
|
||||
"block/" + stateId.getPath() + "_" + doorType
|
||||
);
|
||||
registerBlockModel(stateId, modelId, blockState, modelCache);
|
||||
return ModelsHelper.createMultiVariant(modelId, rotation.getRotation(), false);
|
||||
}
|
||||
|
||||
protected DoorType getDoorType(BlockState blockState) {
|
||||
boolean isHinge = isHinge(blockState.getValue(HINGE), blockState.getValue(OPEN));
|
||||
switch (blockState.getValue(HALF)) {
|
||||
case UPPER: {
|
||||
return isHinge ? DoorType.TOP_HINGE : DoorType.TOP;
|
||||
}
|
||||
case LOWER: {
|
||||
return isHinge ? DoorType.BOTTOM_HINGE : DoorType.BOTTOM;
|
||||
}
|
||||
}
|
||||
return DoorType.BOTTOM;
|
||||
}
|
||||
|
||||
private boolean isHinge(DoorHingeSide hingeSide, boolean open) {
|
||||
boolean isHinge = hingeSide == DoorHingeSide.RIGHT;
|
||||
return isHinge && !open || !isHinge && open;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTags(List<TagKey<Block>> blockTags, List<TagKey<Item>> itemTags) {
|
||||
blockTags.add(NamedBlockTags.DOORS);
|
||||
itemTags.add(NamedItemTags.DOORS);
|
||||
}
|
||||
|
||||
protected enum DoorType implements StringRepresentable {
|
||||
BOTTOM_HINGE("bottom_hinge"), TOP_HINGE("top_hinge"), BOTTOM("bottom"), TOP("top");
|
||||
|
||||
private final String name;
|
||||
|
||||
DoorType(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public boolean isHinge() {
|
||||
return this == BOTTOM_HINGE || this == TOP_HINGE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getSerializedName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSerializedName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
166
src/main/java/org/betterx/bclib/blocks/BaseDoublePlantBlock.java
Normal file
166
src/main/java/org/betterx/bclib/blocks/BaseDoublePlantBlock.java
Normal file
|
@ -0,0 +1,166 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.item.ItemEntity;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.enchantment.EnchantmentHelper;
|
||||
import net.minecraft.world.item.enchantment.Enchantments;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
import net.minecraft.world.level.LevelReader;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.BonemealableBlock;
|
||||
import net.minecraft.world.level.block.SoundType;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.StateDefinition;
|
||||
import net.minecraft.world.level.block.state.properties.BooleanProperty;
|
||||
import net.minecraft.world.level.block.state.properties.IntegerProperty;
|
||||
import net.minecraft.world.level.material.Material;
|
||||
import net.minecraft.world.level.storage.loot.LootContext;
|
||||
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import org.betterx.bclib.client.render.BCLRenderLayer;
|
||||
import org.betterx.bclib.interfaces.RenderLayerProvider;
|
||||
import org.betterx.bclib.items.tool.BaseShearsItem;
|
||||
import org.betterx.bclib.util.BlocksHelper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public abstract class BaseDoublePlantBlock extends BaseBlockNotFull implements RenderLayerProvider, BonemealableBlock {
|
||||
private static final VoxelShape SHAPE = box(4, 2, 4, 12, 16, 12);
|
||||
public static final IntegerProperty ROTATION = BlockProperties.ROTATION;
|
||||
public static final BooleanProperty TOP = BooleanProperty.create("top");
|
||||
|
||||
public BaseDoublePlantBlock() {
|
||||
this(
|
||||
FabricBlockSettings.of(Material.PLANT)
|
||||
.sound(SoundType.GRASS)
|
||||
.noCollission()
|
||||
.offsetType(BlockBehaviour.OffsetType.XZ)
|
||||
);
|
||||
}
|
||||
|
||||
public BaseDoublePlantBlock(int light) {
|
||||
this(
|
||||
FabricBlockSettings.of(Material.PLANT)
|
||||
.sound(SoundType.GRASS)
|
||||
.lightLevel((state) -> state.getValue(TOP) ? light : 0)
|
||||
.noCollission()
|
||||
.offsetType(BlockBehaviour.OffsetType.XZ)
|
||||
);
|
||||
}
|
||||
|
||||
public BaseDoublePlantBlock(BlockBehaviour.Properties properties) {
|
||||
super(properties);
|
||||
this.registerDefaultState(this.stateDefinition.any().setValue(TOP, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> stateManager) {
|
||||
stateManager.add(TOP, ROTATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) {
|
||||
Vec3 vec3d = state.getOffset(view, pos);
|
||||
return SHAPE.move(vec3d.x, vec3d.y, vec3d.z);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) {
|
||||
BlockState down = world.getBlockState(pos.below());
|
||||
BlockState up = world.getBlockState(pos.above());
|
||||
return state.getValue(TOP) ? down.getBlock() == this : isTerrain(down) && (up.getMaterial().isReplaceable());
|
||||
}
|
||||
|
||||
public boolean canStayAt(BlockState state, LevelReader world, BlockPos pos) {
|
||||
BlockState down = world.getBlockState(pos.below());
|
||||
BlockState up = world.getBlockState(pos.above());
|
||||
return state.getValue(TOP) ? down.getBlock() == this : isTerrain(down) && (up.getBlock() == this);
|
||||
}
|
||||
|
||||
protected abstract boolean isTerrain(BlockState state);
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public BlockState updateShape(BlockState state,
|
||||
Direction facing,
|
||||
BlockState neighborState,
|
||||
LevelAccessor world,
|
||||
BlockPos pos,
|
||||
BlockPos neighborPos) {
|
||||
if (!canStayAt(state, world, pos)) {
|
||||
return Blocks.AIR.defaultBlockState();
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
|
||||
if (state.getValue(TOP)) {
|
||||
return Lists.newArrayList();
|
||||
}
|
||||
|
||||
ItemStack tool = builder.getParameter(LootContextParams.TOOL);
|
||||
//TODO: 1.18.2 Test if shearing still works
|
||||
if (tool != null && BaseShearsItem.isShear(tool) || EnchantmentHelper.getItemEnchantmentLevel(
|
||||
Enchantments.SILK_TOUCH,
|
||||
tool
|
||||
) > 0) {
|
||||
return Lists.newArrayList(new ItemStack(this));
|
||||
} else {
|
||||
return Lists.newArrayList();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BCLRenderLayer getRenderLayer() {
|
||||
return BCLRenderLayer.CUTOUT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidBonemealTarget(BlockGetter world, BlockPos pos, BlockState state, boolean isClient) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBonemealSuccess(Level level, RandomSource random, BlockPos pos, BlockState state) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
|
||||
ItemEntity item = new ItemEntity(
|
||||
level,
|
||||
pos.getX() + 0.5,
|
||||
pos.getY() + 0.5,
|
||||
pos.getZ() + 0.5,
|
||||
new ItemStack(this)
|
||||
);
|
||||
level.addFreshEntity(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlacedBy(Level world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack itemStack) {
|
||||
int rot = world.random.nextInt(4);
|
||||
BlockState bs = this.defaultBlockState().setValue(ROTATION, rot);
|
||||
BlocksHelper.setWithoutUpdate(world, pos, bs);
|
||||
BlocksHelper.setWithoutUpdate(world, pos.above(), bs.setValue(TOP, true));
|
||||
}
|
||||
}
|
97
src/main/java/org/betterx/bclib/blocks/BaseFenceBlock.java
Normal file
97
src/main/java/org/betterx/bclib/blocks/BaseFenceBlock.java
Normal file
|
@ -0,0 +1,97 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.client.resources.model.BlockModelRotation;
|
||||
import net.minecraft.client.resources.model.UnbakedModel;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.FenceBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.storage.loot.LootContext;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
|
||||
import org.betterx.bclib.client.models.BasePatterns;
|
||||
import org.betterx.bclib.client.models.ModelsHelper;
|
||||
import org.betterx.bclib.client.models.PatternsHelper;
|
||||
import org.betterx.bclib.interfaces.BlockModelProvider;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class BaseFenceBlock extends FenceBlock implements BlockModelProvider {
|
||||
private final Block parent;
|
||||
|
||||
public BaseFenceBlock(Block source) {
|
||||
super(FabricBlockSettings.copyOf(source).noOcclusion());
|
||||
this.parent = source;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
|
||||
return Collections.singletonList(new ItemStack(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public BlockModel getItemModel(ResourceLocation blockId) {
|
||||
ResourceLocation parentId = Registry.BLOCK.getKey(parent);
|
||||
Optional<String> pattern = PatternsHelper.createJson(BasePatterns.ITEM_FENCE, parentId);
|
||||
return ModelsHelper.fromPattern(pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) {
|
||||
ResourceLocation parentId = Registry.BLOCK.getKey(parent);
|
||||
String path = blockId.getPath();
|
||||
Optional<String> pattern = Optional.empty();
|
||||
if (path.endsWith("_post")) {
|
||||
pattern = PatternsHelper.createJson(BasePatterns.BLOCK_FENCE_POST, parentId);
|
||||
}
|
||||
if (path.endsWith("_side")) {
|
||||
pattern = PatternsHelper.createJson(BasePatterns.BLOCK_FENCE_SIDE, parentId);
|
||||
}
|
||||
return ModelsHelper.fromPattern(pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public UnbakedModel getModelVariant(ResourceLocation stateId,
|
||||
BlockState blockState,
|
||||
Map<ResourceLocation, UnbakedModel> modelCache) {
|
||||
ResourceLocation postId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + "_post");
|
||||
ResourceLocation sideId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + "_side");
|
||||
registerBlockModel(postId, postId, blockState, modelCache);
|
||||
registerBlockModel(sideId, sideId, blockState, modelCache);
|
||||
|
||||
ModelsHelper.MultiPartBuilder builder = ModelsHelper.MultiPartBuilder.create(stateDefinition);
|
||||
builder.part(sideId).setCondition(state -> state.getValue(NORTH)).setUVLock(true).add();
|
||||
builder.part(sideId)
|
||||
.setCondition(state -> state.getValue(EAST))
|
||||
.setTransformation(BlockModelRotation.X0_Y90.getRotation())
|
||||
.setUVLock(true)
|
||||
.add();
|
||||
builder.part(sideId)
|
||||
.setCondition(state -> state.getValue(SOUTH))
|
||||
.setTransformation(BlockModelRotation.X0_Y180.getRotation())
|
||||
.setUVLock(true)
|
||||
.add();
|
||||
builder.part(sideId)
|
||||
.setCondition(state -> state.getValue(WEST))
|
||||
.setTransformation(BlockModelRotation.X0_Y270.getRotation())
|
||||
.setUVLock(true)
|
||||
.add();
|
||||
builder.part(postId).add();
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
}
|
140
src/main/java/org/betterx/bclib/blocks/BaseFurnaceBlock.java
Normal file
140
src/main/java/org/betterx/bclib/blocks/BaseFurnaceBlock.java
Normal file
|
@ -0,0 +1,140 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.client.resources.model.UnbakedModel;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.stats.Stats;
|
||||
import net.minecraft.world.MenuProvider;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.FurnaceBlock;
|
||||
import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityTicker;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.storage.loot.LootContext;
|
||||
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import org.betterx.bclib.blockentities.BaseFurnaceBlockEntity;
|
||||
import org.betterx.bclib.client.models.BasePatterns;
|
||||
import org.betterx.bclib.client.models.ModelsHelper;
|
||||
import org.betterx.bclib.client.models.PatternsHelper;
|
||||
import org.betterx.bclib.client.render.BCLRenderLayer;
|
||||
import org.betterx.bclib.interfaces.BlockModelProvider;
|
||||
import org.betterx.bclib.interfaces.RenderLayerProvider;
|
||||
import org.betterx.bclib.registry.BaseBlockEntities;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class BaseFurnaceBlock extends FurnaceBlock implements BlockModelProvider, RenderLayerProvider {
|
||||
public BaseFurnaceBlock(Block source) {
|
||||
this(FabricBlockSettings.copyOf(source).lightLevel(state -> state.getValue(LIT) ? 13 : 0));
|
||||
}
|
||||
|
||||
public BaseFurnaceBlock(BlockBehaviour.Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) {
|
||||
return new BaseFurnaceBlockEntity(blockPos, blockState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void openContainer(Level world, BlockPos pos, Player player) {
|
||||
BlockEntity blockEntity = world.getBlockEntity(pos);
|
||||
if (blockEntity instanceof BaseFurnaceBlockEntity) {
|
||||
player.openMenu((MenuProvider) blockEntity);
|
||||
player.awardStat(Stats.INTERACT_WITH_FURNACE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) {
|
||||
String blockName = blockId.getPath();
|
||||
Map<String, String> textures = Maps.newHashMap();
|
||||
textures.put("%modid%", blockId.getNamespace());
|
||||
textures.put("%top%", blockName + "_top");
|
||||
textures.put("%side%", blockName + "_side");
|
||||
Optional<String> pattern;
|
||||
if (blockState.getValue(LIT)) {
|
||||
textures.put("%front%", blockName + "_front_on");
|
||||
textures.put("%glow%", blockName + "_glow");
|
||||
pattern = PatternsHelper.createJson(BasePatterns.BLOCK_FURNACE_LIT, textures);
|
||||
} else {
|
||||
textures.put("%front%", blockName + "_front");
|
||||
pattern = PatternsHelper.createJson(BasePatterns.BLOCK_FURNACE, textures);
|
||||
}
|
||||
return ModelsHelper.fromPattern(pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public BlockModel getItemModel(ResourceLocation resourceLocation) {
|
||||
return getBlockModel(resourceLocation, defaultBlockState());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public UnbakedModel getModelVariant(ResourceLocation stateId,
|
||||
BlockState blockState,
|
||||
Map<ResourceLocation, UnbakedModel> modelCache) {
|
||||
String lit = blockState.getValue(LIT) ? "_lit" : "";
|
||||
ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + lit);
|
||||
registerBlockModel(stateId, modelId, blockState, modelCache);
|
||||
return ModelsHelper.createFacingModel(modelId, blockState.getValue(FACING), false, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BCLRenderLayer getRenderLayer() {
|
||||
return BCLRenderLayer.CUTOUT;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
|
||||
List<ItemStack> drop = Lists.newArrayList(new ItemStack(this));
|
||||
BlockEntity blockEntity = builder.getOptionalParameter(LootContextParams.BLOCK_ENTITY);
|
||||
if (blockEntity instanceof BaseFurnaceBlockEntity) {
|
||||
BaseFurnaceBlockEntity entity = (BaseFurnaceBlockEntity) blockEntity;
|
||||
for (int i = 0; i < entity.getContainerSize(); i++) {
|
||||
drop.add(entity.getItem(i));
|
||||
}
|
||||
}
|
||||
return drop;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level,
|
||||
BlockState blockState,
|
||||
BlockEntityType<T> blockEntityType) {
|
||||
return createFurnaceTicker(level, blockEntityType, BaseBlockEntities.FURNACE);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected static <T extends BlockEntity> BlockEntityTicker<T> createFurnaceTicker(Level level,
|
||||
BlockEntityType<T> blockEntityType,
|
||||
BlockEntityType<? extends AbstractFurnaceBlockEntity> blockEntityType2) {
|
||||
return level.isClientSide ? null : createTickerHelper(
|
||||
blockEntityType,
|
||||
blockEntityType2,
|
||||
AbstractFurnaceBlockEntity::serverTick
|
||||
);
|
||||
}
|
||||
}
|
85
src/main/java/org/betterx/bclib/blocks/BaseGateBlock.java
Normal file
85
src/main/java/org/betterx/bclib/blocks/BaseGateBlock.java
Normal file
|
@ -0,0 +1,85 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.client.resources.model.UnbakedModel;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.FenceGateBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.storage.loot.LootContext;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
|
||||
import org.betterx.bclib.client.models.BasePatterns;
|
||||
import org.betterx.bclib.client.models.ModelsHelper;
|
||||
import org.betterx.bclib.client.models.PatternsHelper;
|
||||
import org.betterx.bclib.interfaces.BlockModelProvider;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class BaseGateBlock extends FenceGateBlock implements BlockModelProvider {
|
||||
private final Block parent;
|
||||
|
||||
public BaseGateBlock(Block source) {
|
||||
super(FabricBlockSettings.copyOf(source).noOcclusion());
|
||||
this.parent = source;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
|
||||
return Collections.singletonList(new ItemStack(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public BlockModel getItemModel(ResourceLocation resourceLocation) {
|
||||
return getBlockModel(resourceLocation, defaultBlockState());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) {
|
||||
boolean inWall = blockState.getValue(IN_WALL);
|
||||
boolean isOpen = blockState.getValue(OPEN);
|
||||
ResourceLocation parentId = Registry.BLOCK.getKey(parent);
|
||||
Optional<String> pattern;
|
||||
if (inWall) {
|
||||
pattern = isOpen
|
||||
? PatternsHelper.createJson(
|
||||
BasePatterns.BLOCK_GATE_OPEN_WALL,
|
||||
parentId
|
||||
)
|
||||
: PatternsHelper.createJson(BasePatterns.BLOCK_GATE_CLOSED_WALL, parentId);
|
||||
} else {
|
||||
pattern = isOpen
|
||||
? PatternsHelper.createJson(
|
||||
BasePatterns.BLOCK_GATE_OPEN,
|
||||
parentId
|
||||
)
|
||||
: PatternsHelper.createJson(BasePatterns.BLOCK_GATE_CLOSED, parentId);
|
||||
}
|
||||
return ModelsHelper.fromPattern(pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public UnbakedModel getModelVariant(ResourceLocation stateId,
|
||||
BlockState blockState,
|
||||
Map<ResourceLocation, UnbakedModel> modelCache) {
|
||||
boolean inWall = blockState.getValue(IN_WALL);
|
||||
boolean isOpen = blockState.getValue(OPEN);
|
||||
String state = "" + (inWall ? "_wall" : "") + (isOpen ? "_open" : "_closed");
|
||||
ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + state);
|
||||
registerBlockModel(stateId, modelId, blockState, modelCache);
|
||||
return ModelsHelper.createFacingModel(modelId, blockState.getValue(FACING), true, false);
|
||||
}
|
||||
}
|
|
@ -1,8 +1,5 @@
|
|||
package ru.bclib.blocks;
|
||||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
@ -13,9 +10,14 @@ import net.minecraft.world.level.block.Block;
|
|||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.storage.loot.LootContext;
|
||||
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
|
||||
import ru.bclib.client.render.BCLRenderLayer;
|
||||
import ru.bclib.interfaces.RenderLayerProvider;
|
||||
import ru.bclib.interfaces.tools.AddMineablePickaxe;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
|
||||
import org.betterx.bclib.client.render.BCLRenderLayer;
|
||||
import org.betterx.bclib.interfaces.RenderLayerProvider;
|
||||
import org.betterx.bclib.interfaces.tools.AddMineablePickaxe;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
@ -24,6 +26,7 @@ public class BaseGlassBlock extends BaseBlockNotFull implements AddMineablePicka
|
|||
public BaseGlassBlock(Block block) {
|
||||
this(block, 0.3f);
|
||||
}
|
||||
|
||||
public BaseGlassBlock(Block block, float resistance) {
|
||||
super(FabricBlockSettings.copyOf(block)
|
||||
.resistance(resistance)
|
||||
|
@ -44,7 +47,7 @@ public class BaseGlassBlock extends BaseBlockNotFull implements AddMineablePicka
|
|||
|
||||
@Environment(EnvType.CLIENT)
|
||||
public boolean skipRendering(BlockState state, BlockState neighbor, Direction facing) {
|
||||
return neighbor.getBlock() == this ? true : super.skipRendering(state, neighbor, facing);
|
||||
return neighbor.getBlock() == this || super.skipRendering(state, neighbor, facing);
|
||||
}
|
||||
|
||||
@Override
|
72
src/main/java/org/betterx/bclib/blocks/BaseLadderBlock.java
Normal file
72
src/main/java/org/betterx/bclib/blocks/BaseLadderBlock.java
Normal file
|
@ -0,0 +1,72 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.client.resources.model.UnbakedModel;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.LadderBlock;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.storage.loot.LootContext;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
|
||||
import org.betterx.bclib.client.models.BasePatterns;
|
||||
import org.betterx.bclib.client.models.ModelsHelper;
|
||||
import org.betterx.bclib.client.models.PatternsHelper;
|
||||
import org.betterx.bclib.client.render.BCLRenderLayer;
|
||||
import org.betterx.bclib.interfaces.BlockModelProvider;
|
||||
import org.betterx.bclib.interfaces.RenderLayerProvider;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class BaseLadderBlock extends LadderBlock implements RenderLayerProvider, BlockModelProvider {
|
||||
public BaseLadderBlock(Block block) {
|
||||
this(FabricBlockSettings.copyOf(block).noOcclusion());
|
||||
}
|
||||
|
||||
public BaseLadderBlock(BlockBehaviour.Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BCLRenderLayer getRenderLayer() {
|
||||
return BCLRenderLayer.CUTOUT;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public BlockModel getItemModel(ResourceLocation blockId) {
|
||||
return ModelsHelper.createBlockItem(blockId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) {
|
||||
Optional<String> pattern = PatternsHelper.createJson(BasePatterns.BLOCK_LADDER, blockId);
|
||||
return ModelsHelper.fromPattern(pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public UnbakedModel getModelVariant(ResourceLocation stateId,
|
||||
BlockState blockState,
|
||||
Map<ResourceLocation, UnbakedModel> modelCache) {
|
||||
ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath());
|
||||
registerBlockModel(stateId, modelId, blockState, modelCache);
|
||||
return ModelsHelper.createFacingModel(modelId, blockState.getValue(FACING), false, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
|
||||
return Collections.singletonList(new ItemStack(this));
|
||||
}
|
||||
}
|
118
src/main/java/org/betterx/bclib/blocks/BaseLeavesBlock.java
Normal file
118
src/main/java/org/betterx/bclib/blocks/BaseLeavesBlock.java
Normal file
|
@ -0,0 +1,118 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.enchantment.EnchantmentHelper;
|
||||
import net.minecraft.world.item.enchantment.Enchantments;
|
||||
import net.minecraft.world.level.ItemLike;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.LeavesBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.material.MaterialColor;
|
||||
import net.minecraft.world.level.storage.loot.LootContext;
|
||||
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
|
||||
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import org.betterx.bclib.api.tag.NamedBlockTags;
|
||||
import org.betterx.bclib.api.tag.NamedItemTags;
|
||||
import org.betterx.bclib.client.render.BCLRenderLayer;
|
||||
import org.betterx.bclib.interfaces.BlockModelProvider;
|
||||
import org.betterx.bclib.interfaces.RenderLayerProvider;
|
||||
import org.betterx.bclib.interfaces.TagProvider;
|
||||
import org.betterx.bclib.interfaces.tools.AddMineableHoe;
|
||||
import org.betterx.bclib.interfaces.tools.AddMineableShears;
|
||||
import org.betterx.bclib.items.tool.BaseShearsItem;
|
||||
import org.betterx.bclib.util.MHelper;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class BaseLeavesBlock extends LeavesBlock implements BlockModelProvider, RenderLayerProvider, TagProvider, AddMineableShears, AddMineableHoe {
|
||||
protected final Block sapling;
|
||||
|
||||
private static FabricBlockSettings makeLeaves(MaterialColor color) {
|
||||
return FabricBlockSettings
|
||||
.copyOf(Blocks.OAK_LEAVES)
|
||||
.mapColor(color)
|
||||
//.requiresTool()
|
||||
.allowsSpawning((state, world, pos, type) -> false)
|
||||
.suffocates((state, world, pos) -> false)
|
||||
.blockVision((state, world, pos) -> false);
|
||||
}
|
||||
|
||||
public BaseLeavesBlock(Block sapling, MaterialColor color, Consumer<FabricBlockSettings> customizeProperties) {
|
||||
super(BaseBlock.acceptAndReturn(customizeProperties, makeLeaves(color)));
|
||||
this.sapling = sapling;
|
||||
}
|
||||
|
||||
public BaseLeavesBlock(Block sapling,
|
||||
MaterialColor color,
|
||||
int light,
|
||||
Consumer<FabricBlockSettings> customizeProperties) {
|
||||
super(BaseBlock.acceptAndReturn(customizeProperties, makeLeaves(color).luminance(light)));
|
||||
this.sapling = sapling;
|
||||
}
|
||||
|
||||
public BaseLeavesBlock(Block sapling, MaterialColor color) {
|
||||
super(makeLeaves(color));
|
||||
this.sapling = sapling;
|
||||
}
|
||||
|
||||
public BaseLeavesBlock(Block sapling, MaterialColor color, int light) {
|
||||
super(makeLeaves(color).lightLevel(light));
|
||||
this.sapling = sapling;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BCLRenderLayer getRenderLayer() {
|
||||
return BCLRenderLayer.CUTOUT;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
|
||||
return BaseLeavesBlock.getLeaveDrops(this, this.sapling, builder, 16, 16);
|
||||
}
|
||||
|
||||
public static List<ItemStack> getLeaveDrops(ItemLike leaveBlock,
|
||||
Block sapling,
|
||||
LootContext.Builder builder,
|
||||
int fortuneRate,
|
||||
int dropRate) {
|
||||
ItemStack tool = builder.getParameter(LootContextParams.TOOL);
|
||||
if (tool != null) {
|
||||
if (BaseShearsItem.isShear(tool) || EnchantmentHelper.getItemEnchantmentLevel(
|
||||
Enchantments.SILK_TOUCH,
|
||||
tool
|
||||
) > 0) {
|
||||
return Collections.singletonList(new ItemStack(leaveBlock));
|
||||
}
|
||||
int fortune = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.BLOCK_FORTUNE, tool);
|
||||
if (MHelper.RANDOM.nextInt(fortuneRate) <= fortune) {
|
||||
return Lists.newArrayList(new ItemStack(sapling));
|
||||
}
|
||||
return Lists.newArrayList();
|
||||
}
|
||||
return MHelper.RANDOM.nextInt(dropRate) == 0
|
||||
? Lists.newArrayList(new ItemStack(sapling))
|
||||
: Lists.newArrayList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockModel getItemModel(ResourceLocation resourceLocation) {
|
||||
return getBlockModel(resourceLocation, defaultBlockState());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTags(List<TagKey<Block>> blockTags, List<TagKey<Item>> itemTags) {
|
||||
blockTags.add(NamedBlockTags.LEAVES);
|
||||
itemTags.add(NamedItemTags.LEAVES);
|
||||
}
|
||||
}
|
128
src/main/java/org/betterx/bclib/blocks/BaseMetalBarsBlock.java
Normal file
128
src/main/java/org/betterx/bclib/blocks/BaseMetalBarsBlock.java
Normal file
|
@ -0,0 +1,128 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.client.resources.model.BlockModelRotation;
|
||||
import net.minecraft.client.resources.model.UnbakedModel;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.IronBarsBlock;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.storage.loot.LootContext;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
|
||||
import org.betterx.bclib.client.models.BasePatterns;
|
||||
import org.betterx.bclib.client.models.ModelsHelper;
|
||||
import org.betterx.bclib.client.models.PatternsHelper;
|
||||
import org.betterx.bclib.client.render.BCLRenderLayer;
|
||||
import org.betterx.bclib.interfaces.BlockModelProvider;
|
||||
import org.betterx.bclib.interfaces.RenderLayerProvider;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class BaseMetalBarsBlock extends IronBarsBlock implements BlockModelProvider, RenderLayerProvider {
|
||||
public BaseMetalBarsBlock(Block source) {
|
||||
this(FabricBlockSettings.copyOf(source).strength(5.0F, 6.0F).noOcclusion());
|
||||
}
|
||||
|
||||
public BaseMetalBarsBlock(BlockBehaviour.Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
|
||||
return Collections.singletonList(new ItemStack(this));
|
||||
}
|
||||
|
||||
public Optional<String> getModelString(String block) {
|
||||
ResourceLocation blockId = Registry.BLOCK.getKey(this);
|
||||
if (block.contains("item")) {
|
||||
return PatternsHelper.createJson(BasePatterns.ITEM_BLOCK, blockId);
|
||||
}
|
||||
if (block.contains("post")) {
|
||||
return PatternsHelper.createJson(BasePatterns.BLOCK_BARS_POST, blockId);
|
||||
} else {
|
||||
return PatternsHelper.createJson(BasePatterns.BLOCK_BARS_SIDE, blockId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public BlockModel getItemModel(ResourceLocation resourceLocation) {
|
||||
return ModelsHelper.createBlockItem(resourceLocation);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) {
|
||||
ResourceLocation thisId = Registry.BLOCK.getKey(this);
|
||||
String path = blockId.getPath();
|
||||
Optional<String> pattern = Optional.empty();
|
||||
if (path.endsWith("_post")) {
|
||||
pattern = PatternsHelper.createJson(BasePatterns.BLOCK_BARS_POST, thisId);
|
||||
}
|
||||
if (path.endsWith("_side")) {
|
||||
pattern = PatternsHelper.createJson(BasePatterns.BLOCK_BARS_SIDE, thisId);
|
||||
}
|
||||
return ModelsHelper.fromPattern(pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public UnbakedModel getModelVariant(ResourceLocation stateId,
|
||||
BlockState blockState,
|
||||
Map<ResourceLocation, UnbakedModel> modelCache) {
|
||||
ResourceLocation postId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + "_post");
|
||||
ResourceLocation sideId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + "_side");
|
||||
registerBlockModel(postId, postId, blockState, modelCache);
|
||||
registerBlockModel(sideId, sideId, blockState, modelCache);
|
||||
|
||||
ModelsHelper.MultiPartBuilder builder = ModelsHelper.MultiPartBuilder.create(stateDefinition);
|
||||
builder.part(postId)
|
||||
.setCondition(state -> !state.getValue(NORTH) && !state.getValue(EAST) && !state.getValue(SOUTH) && !state
|
||||
.getValue(WEST))
|
||||
.add();
|
||||
builder.part(sideId).setCondition(state -> state.getValue(NORTH)).setUVLock(true).add();
|
||||
builder.part(sideId)
|
||||
.setCondition(state -> state.getValue(EAST))
|
||||
.setTransformation(BlockModelRotation.X0_Y90.getRotation())
|
||||
.setUVLock(true)
|
||||
.add();
|
||||
builder.part(sideId)
|
||||
.setCondition(state -> state.getValue(SOUTH))
|
||||
.setTransformation(BlockModelRotation.X0_Y180.getRotation())
|
||||
.setUVLock(true)
|
||||
.add();
|
||||
builder.part(sideId)
|
||||
.setCondition(state -> state.getValue(WEST))
|
||||
.setTransformation(BlockModelRotation.X0_Y270.getRotation())
|
||||
.setUVLock(true)
|
||||
.add();
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
public boolean skipRendering(BlockState state, BlockState stateFrom, Direction direction) {
|
||||
if (direction.getAxis().isVertical() && stateFrom.getBlock() == this && !stateFrom.equals(state)) {
|
||||
return false;
|
||||
}
|
||||
return super.skipRendering(state, stateFrom, direction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BCLRenderLayer getRenderLayer() {
|
||||
return BCLRenderLayer.CUTOUT;
|
||||
}
|
||||
}
|
125
src/main/java/org/betterx/bclib/blocks/BaseOreBlock.java
Normal file
125
src/main/java/org/betterx/bclib/blocks/BaseOreBlock.java
Normal file
|
@ -0,0 +1,125 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.util.valueproviders.UniformInt;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.TieredItem;
|
||||
import net.minecraft.world.item.enchantment.EnchantmentHelper;
|
||||
import net.minecraft.world.item.enchantment.Enchantments;
|
||||
import net.minecraft.world.level.ItemLike;
|
||||
import net.minecraft.world.level.block.DropExperienceBlock;
|
||||
import net.minecraft.world.level.block.SoundType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.material.Material;
|
||||
import net.minecraft.world.level.material.MaterialColor;
|
||||
import net.minecraft.world.level.storage.loot.LootContext;
|
||||
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
|
||||
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
|
||||
import org.betterx.bclib.interfaces.BlockModelProvider;
|
||||
import org.betterx.bclib.util.LootUtil;
|
||||
import org.betterx.bclib.util.MHelper;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class BaseOreBlock extends DropExperienceBlock implements BlockModelProvider {
|
||||
private final Supplier<Item> dropItem;
|
||||
private final int minCount;
|
||||
private final int maxCount;
|
||||
private final int miningLevel;
|
||||
|
||||
public BaseOreBlock(Supplier<Item> drop, int minCount, int maxCount, int experience) {
|
||||
this(drop, minCount, maxCount, experience, 0);
|
||||
}
|
||||
|
||||
public BaseOreBlock(Supplier<Item> drop, int minCount, int maxCount, int experience, int miningLevel) {
|
||||
this(
|
||||
FabricBlockSettings
|
||||
.of(Material.STONE, MaterialColor.SAND)
|
||||
.requiresTool()
|
||||
.destroyTime(3F)
|
||||
.explosionResistance(9F)
|
||||
.sound(SoundType.STONE),
|
||||
drop, minCount, maxCount, experience, miningLevel
|
||||
);
|
||||
}
|
||||
|
||||
public BaseOreBlock(Properties properties, Supplier<Item> drop, int minCount, int maxCount, int experience) {
|
||||
this(properties, drop, minCount, maxCount, experience, 0);
|
||||
}
|
||||
|
||||
public BaseOreBlock(Properties properties,
|
||||
Supplier<Item> drop,
|
||||
int minCount,
|
||||
int maxCount,
|
||||
int experience,
|
||||
int miningLevel) {
|
||||
super(properties, UniformInt.of(experience > 0 ? 1 : 0, experience));
|
||||
this.dropItem = drop;
|
||||
this.minCount = minCount;
|
||||
this.maxCount = maxCount;
|
||||
this.miningLevel = miningLevel;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
|
||||
return LootUtil
|
||||
.getDrops(this, state, builder)
|
||||
.orElseGet(
|
||||
() -> BaseOreBlock.getDroppedItems(this,
|
||||
dropItem.get(),
|
||||
maxCount,
|
||||
minCount,
|
||||
miningLevel,
|
||||
state,
|
||||
builder)
|
||||
);
|
||||
}
|
||||
|
||||
public static List<ItemStack> getDroppedItems(ItemLike block,
|
||||
Item dropItem,
|
||||
int maxCount,
|
||||
int minCount,
|
||||
int miningLevel,
|
||||
BlockState state,
|
||||
LootContext.Builder builder) {
|
||||
ItemStack tool = builder.getParameter(LootContextParams.TOOL);
|
||||
if (tool != null && tool.isCorrectToolForDrops(state)) {
|
||||
boolean canMine = miningLevel == 0;
|
||||
if (tool.getItem() instanceof TieredItem tired) {
|
||||
canMine = tired.getTier().getLevel() >= miningLevel;
|
||||
}
|
||||
if (canMine) {
|
||||
if (EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool) > 0) {
|
||||
return Collections.singletonList(new ItemStack(block));
|
||||
}
|
||||
int count;
|
||||
int enchantment = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.BLOCK_FORTUNE, tool);
|
||||
if (enchantment > 0) {
|
||||
int min = Mth.clamp(minCount + enchantment, minCount, maxCount);
|
||||
int max = maxCount + (enchantment / Enchantments.BLOCK_FORTUNE.getMaxLevel());
|
||||
if (min == max) {
|
||||
return Collections.singletonList(new ItemStack(dropItem, max));
|
||||
}
|
||||
count = MHelper.randRange(min, max, MHelper.RANDOM_SOURCE);
|
||||
} else {
|
||||
count = MHelper.randRange(minCount, maxCount, MHelper.RANDOM_SOURCE);
|
||||
}
|
||||
return Collections.singletonList(new ItemStack(dropItem, count));
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockModel getItemModel(ResourceLocation resourceLocation) {
|
||||
return getBlockModel(resourceLocation, defaultBlockState());
|
||||
}
|
||||
}
|
101
src/main/java/org/betterx/bclib/blocks/BasePathBlock.java
Normal file
101
src/main/java/org/betterx/bclib/blocks/BasePathBlock.java
Normal file
|
@ -0,0 +1,101 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.client.resources.model.UnbakedModel;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.enchantment.EnchantmentHelper;
|
||||
import net.minecraft.world.item.enchantment.Enchantments;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.storage.loot.LootContext;
|
||||
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
|
||||
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import org.betterx.bclib.client.models.BasePatterns;
|
||||
import org.betterx.bclib.client.models.ModelsHelper;
|
||||
import org.betterx.bclib.client.models.PatternsHelper;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class BasePathBlock extends BaseBlockNotFull {
|
||||
private static final VoxelShape SHAPE = box(0, 0, 0, 16, 15, 16);
|
||||
|
||||
private Block baseBlock;
|
||||
|
||||
public BasePathBlock(Block source) {
|
||||
super(FabricBlockSettings.copyOf(source).isValidSpawn((state, world, pos, type) -> false));
|
||||
this.baseBlock = Blocks.DIRT;
|
||||
if (source instanceof BaseTerrainBlock) {
|
||||
BaseTerrainBlock terrain = (BaseTerrainBlock) source;
|
||||
this.baseBlock = terrain.getBaseBlock();
|
||||
terrain.setPathBlock(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
|
||||
ItemStack tool = builder.getParameter(LootContextParams.TOOL);
|
||||
if (tool != null && EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool) > 0) {
|
||||
return Collections.singletonList(new ItemStack(this));
|
||||
}
|
||||
return Collections.singletonList(new ItemStack(Blocks.END_STONE));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) {
|
||||
return SHAPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public VoxelShape getCollisionShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) {
|
||||
return SHAPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public BlockModel getItemModel(ResourceLocation blockId) {
|
||||
return getBlockModel(blockId, defaultBlockState());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) {
|
||||
String name = blockId.getPath();
|
||||
ResourceLocation bottomId = Registry.BLOCK.getKey(baseBlock);
|
||||
String bottom = bottomId.getNamespace() + ":block/" + bottomId.getPath();
|
||||
Map<String, String> textures = Maps.newHashMap();
|
||||
textures.put("%modid%", blockId.getNamespace());
|
||||
textures.put("%top%", name + "_top");
|
||||
textures.put("%side%", name.replace("_path", "") + "_side");
|
||||
textures.put("%bottom%", bottom);
|
||||
Optional<String> pattern = PatternsHelper.createJson(BasePatterns.BLOCK_PATH, textures);
|
||||
return ModelsHelper.fromPattern(pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public UnbakedModel getModelVariant(ResourceLocation stateId,
|
||||
BlockState blockState,
|
||||
Map<ResourceLocation, UnbakedModel> modelCache) {
|
||||
ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath());
|
||||
registerBlockModel(stateId, modelId, blockState, modelCache);
|
||||
return ModelsHelper.createRandomTopModel(modelId);
|
||||
}
|
||||
}
|
181
src/main/java/org/betterx/bclib/blocks/BasePlantBlock.java
Normal file
181
src/main/java/org/betterx/bclib/blocks/BasePlantBlock.java
Normal file
|
@ -0,0 +1,181 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.entity.item.ItemEntity;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.enchantment.EnchantmentHelper;
|
||||
import net.minecraft.world.item.enchantment.Enchantments;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
import net.minecraft.world.level.LevelReader;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.BonemealableBlock;
|
||||
import net.minecraft.world.level.block.SoundType;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.material.Material;
|
||||
import net.minecraft.world.level.storage.loot.LootContext;
|
||||
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import org.betterx.bclib.client.models.BasePatterns;
|
||||
import org.betterx.bclib.client.models.ModelsHelper;
|
||||
import org.betterx.bclib.client.models.PatternsHelper;
|
||||
import org.betterx.bclib.client.render.BCLRenderLayer;
|
||||
import org.betterx.bclib.interfaces.RenderLayerProvider;
|
||||
import org.betterx.bclib.items.tool.BaseShearsItem;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public abstract class BasePlantBlock extends BaseBlockNotFull implements RenderLayerProvider, BonemealableBlock {
|
||||
private static final VoxelShape SHAPE = box(4, 0, 4, 12, 14, 12);
|
||||
|
||||
public BasePlantBlock() {
|
||||
this(false, p -> p);
|
||||
}
|
||||
|
||||
public BasePlantBlock(int light) {
|
||||
this(light, p -> p);
|
||||
}
|
||||
|
||||
public BasePlantBlock(int light, Function<Properties, Properties> propMod) {
|
||||
this(false, light, propMod);
|
||||
}
|
||||
|
||||
public BasePlantBlock(boolean replaceabled) {
|
||||
this(replaceabled, p -> p);
|
||||
}
|
||||
|
||||
public BasePlantBlock(boolean replaceable, Function<Properties, Properties> propMod) {
|
||||
this(
|
||||
propMod.apply(FabricBlockSettings
|
||||
.of(replaceable ? Material.REPLACEABLE_PLANT : Material.PLANT)
|
||||
.sound(SoundType.GRASS)
|
||||
.noCollission()
|
||||
.offsetType(BlockBehaviour.OffsetType.XZ)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public BasePlantBlock(boolean replaceable, int light) {
|
||||
this(replaceable, light, p -> p);
|
||||
}
|
||||
|
||||
public BasePlantBlock(boolean replaceable, int light, Function<Properties, Properties> propMod) {
|
||||
this(
|
||||
propMod.apply(FabricBlockSettings
|
||||
.of(replaceable ? Material.REPLACEABLE_PLANT : Material.PLANT)
|
||||
.luminance(light)
|
||||
.sound(SoundType.GRASS)
|
||||
.noCollission()
|
||||
.offsetType(BlockBehaviour.OffsetType.XZ)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public BasePlantBlock(Properties settings) {
|
||||
super(settings.offsetType(BlockBehaviour.OffsetType.XZ));
|
||||
}
|
||||
|
||||
protected abstract boolean isTerrain(BlockState state);
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) {
|
||||
Vec3 vec3d = state.getOffset(view, pos);
|
||||
return SHAPE.move(vec3d.x, vec3d.y, vec3d.z);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) {
|
||||
BlockState down = world.getBlockState(pos.below());
|
||||
return isTerrain(down);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public BlockState updateShape(BlockState state,
|
||||
Direction facing,
|
||||
BlockState neighborState,
|
||||
LevelAccessor world,
|
||||
BlockPos pos,
|
||||
BlockPos neighborPos) {
|
||||
if (!canSurvive(state, world, pos)) {
|
||||
return Blocks.AIR.defaultBlockState();
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
|
||||
ItemStack tool = builder.getParameter(LootContextParams.TOOL);
|
||||
//TODO: 1.18.2 Test if shearing still works
|
||||
if (tool != null && BaseShearsItem.isShear(tool) || EnchantmentHelper.getItemEnchantmentLevel(
|
||||
Enchantments.SILK_TOUCH,
|
||||
tool
|
||||
) > 0) {
|
||||
return Lists.newArrayList(new ItemStack(this));
|
||||
} else {
|
||||
return Lists.newArrayList();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BCLRenderLayer getRenderLayer() {
|
||||
return BCLRenderLayer.CUTOUT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidBonemealTarget(BlockGetter world, BlockPos pos, BlockState state, boolean isClient) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBonemealSuccess(Level level, RandomSource random, BlockPos pos, BlockState state) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
|
||||
ItemEntity item = new ItemEntity(
|
||||
level,
|
||||
pos.getX() + 0.5,
|
||||
pos.getY() + 0.5,
|
||||
pos.getZ() + 0.5,
|
||||
new ItemStack(this)
|
||||
);
|
||||
level.addFreshEntity(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public BlockModel getItemModel(ResourceLocation resourceLocation) {
|
||||
return ModelsHelper.createBlockItem(resourceLocation);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
@Environment(EnvType.CLIENT)
|
||||
public BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) {
|
||||
Optional<String> pattern = PatternsHelper.createJson(BasePatterns.BLOCK_CROSS, resourceLocation);
|
||||
return ModelsHelper.fromPattern(pattern);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.WorldGenLevel;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.SoundType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.StateDefinition;
|
||||
import net.minecraft.world.level.block.state.properties.IntegerProperty;
|
||||
import net.minecraft.world.level.material.Material;
|
||||
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
public abstract class BasePlantWithAgeBlock extends BasePlantBlock {
|
||||
public static final IntegerProperty AGE = BlockProperties.AGE;
|
||||
|
||||
public BasePlantWithAgeBlock() {
|
||||
this(p -> p);
|
||||
}
|
||||
|
||||
public BasePlantWithAgeBlock(Function<Properties, Properties> propMod) {
|
||||
this(
|
||||
propMod.apply(FabricBlockSettings.of(Material.PLANT)
|
||||
.sound(SoundType.GRASS)
|
||||
.randomTicks()
|
||||
.noCollission())
|
||||
);
|
||||
}
|
||||
|
||||
public BasePlantWithAgeBlock(Properties settings) {
|
||||
super(settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> stateManager) {
|
||||
stateManager.add(AGE);
|
||||
}
|
||||
|
||||
public abstract void growAdult(WorldGenLevel world, RandomSource random, BlockPos pos);
|
||||
|
||||
@Override
|
||||
public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
|
||||
int age = state.getValue(AGE);
|
||||
if (age < 3) {
|
||||
level.setBlockAndUpdate(pos, state.setValue(AGE, age + 1));
|
||||
} else {
|
||||
growAdult(level, random, pos);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBonemealSuccess(Level level, RandomSource random, BlockPos pos, BlockState state) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
|
||||
super.tick(state, world, pos, random);
|
||||
if (random.nextInt(8) == 0) {
|
||||
performBonemeal(world, random, pos, state);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.client.resources.model.UnbakedModel;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.PressurePlateBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.storage.loot.LootContext;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
|
||||
import org.betterx.bclib.client.models.BasePatterns;
|
||||
import org.betterx.bclib.client.models.ModelsHelper;
|
||||
import org.betterx.bclib.client.models.PatternsHelper;
|
||||
import org.betterx.bclib.interfaces.BlockModelProvider;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class BasePressurePlateBlock extends PressurePlateBlock implements BlockModelProvider {
|
||||
private final Block parent;
|
||||
|
||||
public BasePressurePlateBlock(Sensitivity rule, Block source) {
|
||||
super(rule, FabricBlockSettings.copyOf(source).noCollission().noOcclusion().strength(0.5F));
|
||||
this.parent = source;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
|
||||
return Collections.singletonList(new ItemStack(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public BlockModel getItemModel(ResourceLocation resourceLocation) {
|
||||
return getBlockModel(resourceLocation, defaultBlockState());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) {
|
||||
ResourceLocation parentId = Registry.BLOCK.getKey(parent);
|
||||
Optional<String> pattern;
|
||||
if (blockState.getValue(POWERED)) {
|
||||
pattern = PatternsHelper.createJson(BasePatterns.BLOCK_PLATE_DOWN, parentId);
|
||||
} else {
|
||||
pattern = PatternsHelper.createJson(BasePatterns.BLOCK_PLATE_UP, parentId);
|
||||
}
|
||||
return ModelsHelper.fromPattern(pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public UnbakedModel getModelVariant(ResourceLocation stateId,
|
||||
BlockState blockState,
|
||||
Map<ResourceLocation, UnbakedModel> modelCache) {
|
||||
String state = blockState.getValue(POWERED) ? "_down" : "_up";
|
||||
ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + state);
|
||||
registerBlockModel(stateId, modelId, blockState, modelCache);
|
||||
return ModelsHelper.createBlockSimple(modelId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.client.resources.model.UnbakedModel;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.RotatedPillarBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.storage.loot.LootContext;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
|
||||
import org.betterx.bclib.client.models.ModelsHelper;
|
||||
import org.betterx.bclib.client.models.PatternsHelper;
|
||||
import org.betterx.bclib.interfaces.BlockModelProvider;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class BaseRotatedPillarBlock extends RotatedPillarBlock implements BlockModelProvider {
|
||||
public BaseRotatedPillarBlock(Properties settings) {
|
||||
super(settings);
|
||||
}
|
||||
|
||||
public BaseRotatedPillarBlock(Block block) {
|
||||
this(FabricBlockSettings.copyOf(block));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
|
||||
return Collections.singletonList(new ItemStack(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public BlockModel getItemModel(ResourceLocation blockId) {
|
||||
return getBlockModel(blockId, defaultBlockState());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) {
|
||||
Optional<String> pattern = createBlockPattern(blockId);
|
||||
return ModelsHelper.fromPattern(pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public UnbakedModel getModelVariant(ResourceLocation stateId,
|
||||
BlockState blockState,
|
||||
Map<ResourceLocation, UnbakedModel> modelCache) {
|
||||
ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath());
|
||||
registerBlockModel(stateId, modelId, blockState, modelCache);
|
||||
return ModelsHelper.createRotatedModel(modelId, blockState.getValue(AXIS));
|
||||
}
|
||||
|
||||
protected Optional<String> createBlockPattern(ResourceLocation blockId) {
|
||||
return PatternsHelper.createBlockPillar(blockId);
|
||||
}
|
||||
}
|
198
src/main/java/org/betterx/bclib/blocks/BaseSignBlock.java
Normal file
198
src/main/java/org/betterx/bclib/blocks/BaseSignBlock.java
Normal file
|
@ -0,0 +1,198 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.network.protocol.game.ClientboundOpenSignEditorPacket;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.BlockItem;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.context.BlockPlaceContext;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
import net.minecraft.world.level.LevelReader;
|
||||
import net.minecraft.world.level.block.*;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.StateDefinition;
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
||||
import net.minecraft.world.level.block.state.properties.BooleanProperty;
|
||||
import net.minecraft.world.level.block.state.properties.IntegerProperty;
|
||||
import net.minecraft.world.level.block.state.properties.WoodType;
|
||||
import net.minecraft.world.level.material.Fluid;
|
||||
import net.minecraft.world.level.material.FluidState;
|
||||
import net.minecraft.world.level.material.Fluids;
|
||||
import net.minecraft.world.level.storage.loot.LootContext;
|
||||
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.item.v1.FabricItemSettings;
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
|
||||
import org.betterx.bclib.blockentities.BaseSignBlockEntity;
|
||||
import org.betterx.bclib.client.models.ModelsHelper;
|
||||
import org.betterx.bclib.interfaces.BlockModelProvider;
|
||||
import org.betterx.bclib.interfaces.CustomItemProvider;
|
||||
import org.betterx.bclib.util.BlocksHelper;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class BaseSignBlock extends SignBlock implements BlockModelProvider, CustomItemProvider {
|
||||
public static final IntegerProperty ROTATION = BlockStateProperties.ROTATION_16;
|
||||
public static final BooleanProperty FLOOR = BooleanProperty.create("floor");
|
||||
private static final VoxelShape[] WALL_SHAPES = new VoxelShape[]{
|
||||
Block.box(0.0D, 4.5D, 14.0D, 16.0D, 12.5D, 16.0D),
|
||||
Block.box(0.0D, 4.5D, 0.0D, 2.0D, 12.5D, 16.0D),
|
||||
Block.box(0.0D, 4.5D, 0.0D, 16.0D, 12.5D, 2.0D),
|
||||
Block.box(14.0D, 4.5D, 0.0D, 16.0D, 12.5D, 16.0D)
|
||||
};
|
||||
|
||||
private final Block parent;
|
||||
|
||||
public BaseSignBlock(Block source) {
|
||||
super(FabricBlockSettings.copyOf(source).strength(1.0F, 1.0F).noCollission().noOcclusion(), WoodType.OAK);
|
||||
this.registerDefaultState(this.stateDefinition.any()
|
||||
.setValue(ROTATION, 0)
|
||||
.setValue(FLOOR, false)
|
||||
.setValue(WATERLOGGED, false));
|
||||
this.parent = source;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
|
||||
builder.add(ROTATION, FLOOR, WATERLOGGED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) {
|
||||
return state.getValue(FLOOR) ? SHAPE : WALL_SHAPES[state.getValue(ROTATION) >> 2];
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) {
|
||||
return new BaseSignBlockEntity(blockPos, blockState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlacedBy(Level world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack itemStack) {
|
||||
if (placer instanceof Player) {
|
||||
BaseSignBlockEntity sign = (BaseSignBlockEntity) world.getBlockEntity(pos);
|
||||
if (sign != null) {
|
||||
if (!world.isClientSide) {
|
||||
sign.setAllowedPlayerEditor(placer.getUUID());
|
||||
((ServerPlayer) placer).connection.send(new ClientboundOpenSignEditorPacket(pos));
|
||||
} else {
|
||||
sign.setEditable(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState updateShape(BlockState state,
|
||||
Direction facing,
|
||||
BlockState neighborState,
|
||||
LevelAccessor world,
|
||||
BlockPos pos,
|
||||
BlockPos neighborPos) {
|
||||
if (state.getValue(WATERLOGGED)) {
|
||||
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world));
|
||||
}
|
||||
if (!canSurvive(state, world, pos)) {
|
||||
return state.getValue(WATERLOGGED)
|
||||
? state.getFluidState().createLegacyBlock()
|
||||
: Blocks.AIR.defaultBlockState();
|
||||
}
|
||||
return super.updateShape(state, facing, neighborState, world, pos, neighborPos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) {
|
||||
if (!state.getValue(FLOOR)) {
|
||||
int index = (((state.getValue(ROTATION) >> 2) + 2)) & 3;
|
||||
return world.getBlockState(pos.relative(BlocksHelper.HORIZONTAL[index])).getMaterial().isSolid();
|
||||
} else {
|
||||
return world.getBlockState(pos.below()).getMaterial().isSolid();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getStateForPlacement(BlockPlaceContext ctx) {
|
||||
if (ctx.getClickedFace() == Direction.UP) {
|
||||
FluidState fluidState = ctx.getLevel().getFluidState(ctx.getClickedPos());
|
||||
return this
|
||||
.defaultBlockState()
|
||||
.setValue(FLOOR, true)
|
||||
.setValue(ROTATION, Mth.floor((180.0 + ctx.getRotation() * 16.0 / 360.0) + 0.5 - 12) & 15)
|
||||
.setValue(WATERLOGGED, fluidState.getType() == Fluids.WATER);
|
||||
} else if (ctx.getClickedFace() != Direction.DOWN) {
|
||||
BlockState blockState = this.defaultBlockState();
|
||||
FluidState fluidState = ctx.getLevel().getFluidState(ctx.getClickedPos());
|
||||
LevelReader worldView = ctx.getLevel();
|
||||
BlockPos blockPos = ctx.getClickedPos();
|
||||
Direction[] directions = ctx.getNearestLookingDirections();
|
||||
|
||||
for (Direction direction : directions) {
|
||||
if (direction.getAxis().isHorizontal()) {
|
||||
Direction dir = direction.getOpposite();
|
||||
int rot = Mth.floor((180.0 + dir.toYRot() * 16.0 / 360.0) + 0.5 + 4) & 15;
|
||||
blockState = blockState.setValue(ROTATION, rot);
|
||||
if (blockState.canSurvive(worldView, blockPos)) {
|
||||
return blockState.setValue(FLOOR, false)
|
||||
.setValue(WATERLOGGED, fluidState.getType() == Fluids.WATER);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) {
|
||||
ResourceLocation parentId = Registry.BLOCK.getKey(parent);
|
||||
return ModelsHelper.createBlockEmpty(parentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState rotate(BlockState state, Rotation rotation) {
|
||||
return state.setValue(ROTATION, rotation.rotate(state.getValue(ROTATION), 16));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState mirror(BlockState state, Mirror mirror) {
|
||||
return state.setValue(ROTATION, mirror.mirror(state.getValue(ROTATION), 16));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
|
||||
return Collections.singletonList(new ItemStack(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPlaceLiquid(BlockGetter world, BlockPos pos, BlockState state, Fluid fluid) {
|
||||
return super.canPlaceLiquid(world, pos, state, fluid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean placeLiquid(LevelAccessor world, BlockPos pos, BlockState state, FluidState fluidState) {
|
||||
return super.placeLiquid(world, pos, state, fluidState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockItem getCustomItem(ResourceLocation blockID, FabricItemSettings settings) {
|
||||
return new BlockItem(this, settings.stacksTo(16));
|
||||
}
|
||||
}
|
95
src/main/java/org/betterx/bclib/blocks/BaseSlabBlock.java
Normal file
95
src/main/java/org/betterx/bclib/blocks/BaseSlabBlock.java
Normal file
|
@ -0,0 +1,95 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.client.resources.model.BlockModelRotation;
|
||||
import net.minecraft.client.resources.model.UnbakedModel;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.BlockItem;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.SlabBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.properties.SlabType;
|
||||
import net.minecraft.world.level.storage.loot.LootContext;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.item.v1.FabricItemSettings;
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
|
||||
import org.betterx.bclib.client.models.BasePatterns;
|
||||
import org.betterx.bclib.client.models.ModelsHelper;
|
||||
import org.betterx.bclib.client.models.PatternsHelper;
|
||||
import org.betterx.bclib.interfaces.BlockModelProvider;
|
||||
import org.betterx.bclib.interfaces.CustomItemProvider;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class BaseSlabBlock extends SlabBlock implements BlockModelProvider, CustomItemProvider {
|
||||
private final Block parent;
|
||||
public final boolean fireproof;
|
||||
|
||||
public BaseSlabBlock(Block source) {
|
||||
this(source, false);
|
||||
}
|
||||
|
||||
public BaseSlabBlock(Block source, boolean fireproof) {
|
||||
super(FabricBlockSettings.copyOf(source));
|
||||
this.parent = source;
|
||||
this.fireproof = fireproof;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
|
||||
int count = state.getValue(TYPE) == SlabType.DOUBLE ? 2 : 1;
|
||||
return Collections.singletonList(new ItemStack(this, count));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public BlockModel getItemModel(ResourceLocation resourceLocation) {
|
||||
return getBlockModel(resourceLocation, defaultBlockState());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) {
|
||||
ResourceLocation parentId = Registry.BLOCK.getKey(parent);
|
||||
Optional<String> pattern;
|
||||
if (blockState.getValue(TYPE) == SlabType.DOUBLE) {
|
||||
pattern = PatternsHelper.createBlockSimple(parentId);
|
||||
} else {
|
||||
pattern = PatternsHelper.createJson(BasePatterns.BLOCK_SLAB, parentId);
|
||||
}
|
||||
return ModelsHelper.fromPattern(pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public UnbakedModel getModelVariant(ResourceLocation stateId,
|
||||
BlockState blockState,
|
||||
Map<ResourceLocation, UnbakedModel> modelCache) {
|
||||
SlabType type = blockState.getValue(TYPE);
|
||||
ResourceLocation modelId = new ResourceLocation(
|
||||
stateId.getNamespace(),
|
||||
"block/" + stateId.getPath() + "_" + type
|
||||
);
|
||||
registerBlockModel(stateId, modelId, blockState, modelCache);
|
||||
if (type == SlabType.TOP) {
|
||||
return ModelsHelper.createMultiVariant(modelId, BlockModelRotation.X180_Y0.getRotation(), true);
|
||||
}
|
||||
return ModelsHelper.createBlockSimple(modelId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockItem getCustomItem(ResourceLocation blockID, FabricItemSettings settings) {
|
||||
if (fireproof) settings = settings.fireproof();
|
||||
return new BlockItem(this, settings);
|
||||
}
|
||||
}
|
119
src/main/java/org/betterx/bclib/blocks/BaseStairsBlock.java
Normal file
119
src/main/java/org/betterx/bclib/blocks/BaseStairsBlock.java
Normal file
|
@ -0,0 +1,119 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.client.resources.model.BlockModelRotation;
|
||||
import net.minecraft.client.resources.model.UnbakedModel;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.BlockItem;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.StairBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.properties.Half;
|
||||
import net.minecraft.world.level.block.state.properties.StairsShape;
|
||||
import net.minecraft.world.level.storage.loot.LootContext;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.item.v1.FabricItemSettings;
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
|
||||
import org.betterx.bclib.client.models.BasePatterns;
|
||||
import org.betterx.bclib.client.models.ModelsHelper;
|
||||
import org.betterx.bclib.client.models.PatternsHelper;
|
||||
import org.betterx.bclib.interfaces.BlockModelProvider;
|
||||
import org.betterx.bclib.interfaces.CustomItemProvider;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class BaseStairsBlock extends StairBlock implements BlockModelProvider, CustomItemProvider {
|
||||
private final Block parent;
|
||||
public final boolean fireproof;
|
||||
|
||||
public BaseStairsBlock(Block source) {
|
||||
this(source, false);
|
||||
}
|
||||
|
||||
public BaseStairsBlock(Block source, boolean fireproof) {
|
||||
super(source.defaultBlockState(), FabricBlockSettings.copyOf(source));
|
||||
this.parent = source;
|
||||
this.fireproof = fireproof;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
|
||||
return Collections.singletonList(new ItemStack(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public BlockModel getItemModel(ResourceLocation resourceLocation) {
|
||||
return getBlockModel(resourceLocation, defaultBlockState());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) {
|
||||
ResourceLocation parentId = Registry.BLOCK.getKey(parent);
|
||||
Optional<String> pattern = PatternsHelper.createJson(switch (blockState.getValue(SHAPE)) {
|
||||
case STRAIGHT -> BasePatterns.BLOCK_STAIR;
|
||||
case INNER_LEFT, INNER_RIGHT -> BasePatterns.BLOCK_STAIR_INNER;
|
||||
case OUTER_LEFT, OUTER_RIGHT -> BasePatterns.BLOCK_STAIR_OUTER;
|
||||
}, parentId);
|
||||
return ModelsHelper.fromPattern(pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public UnbakedModel getModelVariant(ResourceLocation stateId,
|
||||
BlockState blockState,
|
||||
Map<ResourceLocation, UnbakedModel> modelCache) {
|
||||
String state;
|
||||
StairsShape shape = blockState.getValue(SHAPE);
|
||||
state = switch (shape) {
|
||||
case INNER_LEFT, INNER_RIGHT -> "_inner";
|
||||
case OUTER_LEFT, OUTER_RIGHT -> "_outer";
|
||||
default -> "";
|
||||
};
|
||||
ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + state);
|
||||
registerBlockModel(stateId, modelId, blockState, modelCache);
|
||||
|
||||
boolean isTop = blockState.getValue(HALF) == Half.TOP;
|
||||
boolean isLeft = shape == StairsShape.INNER_LEFT || shape == StairsShape.OUTER_LEFT;
|
||||
boolean isRight = shape == StairsShape.INNER_RIGHT || shape == StairsShape.OUTER_RIGHT;
|
||||
int y = 0;
|
||||
int x = isTop ? 180 : 0;
|
||||
switch (blockState.getValue(FACING)) {
|
||||
case NORTH:
|
||||
if (isTop && !isRight) y = 270;
|
||||
else if (!isTop) y = isLeft ? 180 : 270;
|
||||
break;
|
||||
case EAST:
|
||||
if (isTop && isRight) y = 90;
|
||||
else if (!isTop && isLeft) y = 270;
|
||||
break;
|
||||
case SOUTH:
|
||||
if (isTop) y = isRight ? 180 : 90;
|
||||
else if (!isLeft) y = 90;
|
||||
break;
|
||||
case WEST:
|
||||
default:
|
||||
y = (isTop && isRight) ? 270 : (!isTop && isLeft) ? 90 : 180;
|
||||
break;
|
||||
}
|
||||
BlockModelRotation rotation = BlockModelRotation.by(x, y);
|
||||
return ModelsHelper.createMultiVariant(modelId, rotation.getRotation(), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockItem getCustomItem(ResourceLocation blockID, FabricItemSettings settings) {
|
||||
if (fireproof) settings = settings.fireproof();
|
||||
return new BlockItem(this, settings);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.sounds.SoundEvent;
|
||||
import net.minecraft.sounds.SoundEvents;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
|
||||
public class BaseStoneButtonBlock extends BaseButtonBlock {
|
||||
public BaseStoneButtonBlock(Block source) {
|
||||
super(source, FabricBlockSettings.copyOf(source).noOcclusion(), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SoundEvent getSound(boolean clicked) {
|
||||
return clicked ? SoundEvents.STONE_BUTTON_CLICK_ON : SoundEvents.STONE_BUTTON_CLICK_OFF;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.sounds.SoundEvents;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.RotatedPillarBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.material.MaterialColor;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
|
||||
import org.betterx.bclib.api.tag.NamedMineableTags;
|
||||
import org.betterx.bclib.api.tag.TagAPI;
|
||||
|
||||
public class BaseStripableLogBlock extends BaseRotatedPillarBlock {
|
||||
private final Block striped;
|
||||
|
||||
public BaseStripableLogBlock(MaterialColor color, Block striped) {
|
||||
super(FabricBlockSettings.copyOf(striped).color(color));
|
||||
this.striped = striped;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public InteractionResult use(BlockState state,
|
||||
Level world,
|
||||
BlockPos pos,
|
||||
Player player,
|
||||
InteractionHand hand,
|
||||
BlockHitResult hit) {
|
||||
if (TagAPI.isToolWithMineableTag(player.getMainHandItem(), NamedMineableTags.AXE)) {
|
||||
world.playSound(player, pos, SoundEvents.AXE_STRIP, SoundSource.BLOCKS, 1.0F, 1.0F);
|
||||
if (!world.isClientSide) {
|
||||
world.setBlock(pos,
|
||||
striped.defaultBlockState()
|
||||
.setValue(RotatedPillarBlock.AXIS, state.getValue(RotatedPillarBlock.AXIS)),
|
||||
11
|
||||
);
|
||||
if (!player.isCreative()) {
|
||||
player.getMainHandItem().hurt(1, world.random, (ServerPlayer) player);
|
||||
}
|
||||
}
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
}
|
162
src/main/java/org/betterx/bclib/blocks/BaseTerrainBlock.java
Normal file
162
src/main/java/org/betterx/bclib/blocks/BaseTerrainBlock.java
Normal file
|
@ -0,0 +1,162 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.client.resources.model.UnbakedModel;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.sounds.SoundEvents;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.enchantment.EnchantmentHelper;
|
||||
import net.minecraft.world.item.enchantment.Enchantments;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.LevelReader;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.SnowLayerBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.lighting.LayerLightEngine;
|
||||
import net.minecraft.world.level.material.MaterialColor;
|
||||
import net.minecraft.world.level.storage.loot.LootContext;
|
||||
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import org.betterx.bclib.api.tag.NamedMineableTags;
|
||||
import org.betterx.bclib.api.tag.TagAPI;
|
||||
import org.betterx.bclib.client.models.BasePatterns;
|
||||
import org.betterx.bclib.client.models.ModelsHelper;
|
||||
import org.betterx.bclib.client.models.PatternsHelper;
|
||||
import org.betterx.bclib.client.sound.BlockSounds;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class BaseTerrainBlock extends BaseBlock {
|
||||
private final Block baseBlock;
|
||||
private Block pathBlock;
|
||||
|
||||
public BaseTerrainBlock(Block baseBlock, MaterialColor color) {
|
||||
super(FabricBlockSettings
|
||||
.copyOf(baseBlock)
|
||||
.materialColor(color)
|
||||
.sound(BlockSounds.TERRAIN_SOUND)
|
||||
.randomTicks()
|
||||
);
|
||||
this.baseBlock = baseBlock;
|
||||
}
|
||||
|
||||
public void setPathBlock(Block roadBlock) {
|
||||
this.pathBlock = roadBlock;
|
||||
}
|
||||
|
||||
public Block getBaseBlock() {
|
||||
return baseBlock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResult use(BlockState state,
|
||||
Level world,
|
||||
BlockPos pos,
|
||||
Player player,
|
||||
InteractionHand hand,
|
||||
BlockHitResult hit) {
|
||||
//TODO: 1.18.2 check
|
||||
if (pathBlock != null && TagAPI.isToolWithMineableTag(player.getMainHandItem(), NamedMineableTags.SHOVEL)) {
|
||||
//if (pathBlock != null && FabricTagProvider.SHOVELS.contains(player.getMainHandItem().getItem())) {
|
||||
world.playSound(player, pos, SoundEvents.SHOVEL_FLATTEN, SoundSource.BLOCKS, 1.0F, 1.0F);
|
||||
if (!world.isClientSide) {
|
||||
world.setBlockAndUpdate(pos, pathBlock.defaultBlockState());
|
||||
if (!player.isCreative()) {
|
||||
player.getMainHandItem().hurt(1, world.random, (ServerPlayer) player);
|
||||
}
|
||||
}
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
|
||||
ItemStack tool = builder.getParameter(LootContextParams.TOOL);
|
||||
if (tool != null && EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool) > 0) {
|
||||
return Collections.singletonList(new ItemStack(this));
|
||||
}
|
||||
return Collections.singletonList(new ItemStack(getBaseBlock()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
|
||||
if (random.nextInt(16) == 0 && !canStay(state, world, pos)) {
|
||||
world.setBlockAndUpdate(pos, getBaseBlock().defaultBlockState());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean canStay(BlockState state, LevelReader worldView, BlockPos pos) {
|
||||
BlockPos blockPos = pos.above();
|
||||
BlockState blockState = worldView.getBlockState(blockPos);
|
||||
if (blockState.is(Blocks.SNOW) && blockState.getValue(SnowLayerBlock.LAYERS) == 1) {
|
||||
return true;
|
||||
} else if (blockState.getFluidState().getAmount() == 8) {
|
||||
return false;
|
||||
} else {
|
||||
int i = LayerLightEngine.getLightBlockInto(
|
||||
worldView,
|
||||
state,
|
||||
pos,
|
||||
blockState,
|
||||
blockPos,
|
||||
Direction.UP,
|
||||
blockState.getLightBlock(worldView, blockPos)
|
||||
);
|
||||
return i < 5;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public BlockModel getItemModel(ResourceLocation blockId) {
|
||||
return getBlockModel(blockId, defaultBlockState());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) {
|
||||
ResourceLocation baseId = Registry.BLOCK.getKey(getBaseBlock());
|
||||
String modId = blockId.getNamespace();
|
||||
String path = blockId.getPath();
|
||||
String bottom = baseId.getNamespace() + ":block/" + baseId.getPath();
|
||||
Map<String, String> textures = Maps.newHashMap();
|
||||
textures.put("%top%", modId + ":block/" + path + "_top");
|
||||
textures.put("%side%", modId + ":block/" + path + "_side");
|
||||
textures.put("%bottom%", bottom);
|
||||
Optional<String> pattern = PatternsHelper.createJson(BasePatterns.BLOCK_TOP_SIDE_BOTTOM, textures);
|
||||
return ModelsHelper.fromPattern(pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public UnbakedModel getModelVariant(ResourceLocation stateId,
|
||||
BlockState blockState,
|
||||
Map<ResourceLocation, UnbakedModel> modelCache) {
|
||||
ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath());
|
||||
registerBlockModel(stateId, modelId, blockState, modelCache);
|
||||
return ModelsHelper.createRandomTopModel(modelId);
|
||||
}
|
||||
}
|
104
src/main/java/org/betterx/bclib/blocks/BaseTrapdoorBlock.java
Normal file
104
src/main/java/org/betterx/bclib/blocks/BaseTrapdoorBlock.java
Normal file
|
@ -0,0 +1,104 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.client.resources.model.BlockModelRotation;
|
||||
import net.minecraft.client.resources.model.UnbakedModel;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.TrapDoorBlock;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.properties.Half;
|
||||
import net.minecraft.world.level.storage.loot.LootContext;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
|
||||
import org.betterx.bclib.client.models.BasePatterns;
|
||||
import org.betterx.bclib.client.models.ModelsHelper;
|
||||
import org.betterx.bclib.client.models.PatternsHelper;
|
||||
import org.betterx.bclib.client.render.BCLRenderLayer;
|
||||
import org.betterx.bclib.interfaces.BlockModelProvider;
|
||||
import org.betterx.bclib.interfaces.RenderLayerProvider;
|
||||
|
||||
import java.util.*;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class BaseTrapdoorBlock extends TrapDoorBlock implements RenderLayerProvider, BlockModelProvider {
|
||||
public BaseTrapdoorBlock(Block source) {
|
||||
this(FabricBlockSettings.copyOf(source).strength(3.0F, 3.0F).noOcclusion());
|
||||
}
|
||||
|
||||
public BaseTrapdoorBlock(BlockBehaviour.Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
|
||||
return Collections.singletonList(new ItemStack(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BCLRenderLayer getRenderLayer() {
|
||||
return BCLRenderLayer.CUTOUT;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public BlockModel getItemModel(ResourceLocation resourceLocation) {
|
||||
return getBlockModel(resourceLocation, defaultBlockState());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) {
|
||||
String name = resourceLocation.getPath();
|
||||
Optional<String> pattern = PatternsHelper.createJson(
|
||||
BasePatterns.BLOCK_TRAPDOOR,
|
||||
new HashMap<String, String>() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
{
|
||||
put("%modid%", resourceLocation.getNamespace());
|
||||
put("%texture%", name);
|
||||
put("%side%", name.replace("trapdoor", "door_side"));
|
||||
}
|
||||
}
|
||||
);
|
||||
return ModelsHelper.fromPattern(pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Environment(EnvType.CLIENT)
|
||||
public UnbakedModel getModelVariant(ResourceLocation stateId,
|
||||
BlockState blockState,
|
||||
Map<ResourceLocation, UnbakedModel> modelCache) {
|
||||
ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath());
|
||||
registerBlockModel(stateId, modelId, blockState, modelCache);
|
||||
boolean isTop = blockState.getValue(HALF) == Half.TOP;
|
||||
boolean isOpen = blockState.getValue(OPEN);
|
||||
int y = 0;
|
||||
int x = (isTop && isOpen) ? 270 : isTop ? 180 : isOpen ? 90 : 0;
|
||||
switch (blockState.getValue(FACING)) {
|
||||
case EAST:
|
||||
y = (isTop && isOpen) ? 270 : 90;
|
||||
break;
|
||||
case NORTH:
|
||||
if (isTop && isOpen) y = 180;
|
||||
break;
|
||||
case SOUTH:
|
||||
y = (isTop && isOpen) ? 0 : 180;
|
||||
break;
|
||||
case WEST:
|
||||
y = (isTop && isOpen) ? 90 : 270;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
BlockModelRotation rotation = BlockModelRotation.by(x, y);
|
||||
return ModelsHelper.createMultiVariant(modelId, rotation.getRotation(), false);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
import net.minecraft.world.level.LevelReader;
|
||||
import net.minecraft.world.level.block.LiquidBlockContainer;
|
||||
import net.minecraft.world.level.block.SoundType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.material.Fluid;
|
||||
import net.minecraft.world.level.material.FluidState;
|
||||
import net.minecraft.world.level.material.Fluids;
|
||||
import net.minecraft.world.level.material.Material;
|
||||
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
|
||||
public abstract class BaseUnderwaterWallPlantBlock extends BaseWallPlantBlock implements LiquidBlockContainer {
|
||||
public BaseUnderwaterWallPlantBlock() {
|
||||
this(
|
||||
FabricBlockSettings
|
||||
.of(Material.WATER_PLANT)
|
||||
.sound(SoundType.WET_GRASS)
|
||||
.noCollission()
|
||||
);
|
||||
}
|
||||
|
||||
public BaseUnderwaterWallPlantBlock(int light) {
|
||||
this(
|
||||
FabricBlockSettings
|
||||
.of(Material.WATER_PLANT)
|
||||
.luminance(light)
|
||||
.sound(SoundType.WET_GRASS)
|
||||
.noCollission()
|
||||
);
|
||||
}
|
||||
|
||||
public BaseUnderwaterWallPlantBlock(Properties settings) {
|
||||
super(settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPlaceLiquid(BlockGetter world, BlockPos pos, BlockState state, Fluid fluid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean placeLiquid(LevelAccessor world, BlockPos pos, BlockState state, FluidState fluidState) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public FluidState getFluidState(BlockState state) {
|
||||
return Fluids.WATER.getSource(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) {
|
||||
return world.getFluidState(pos).getType() == Fluids.WATER && super.canSurvive(state, world, pos);
|
||||
}
|
||||
}
|
160
src/main/java/org/betterx/bclib/blocks/BaseVineBlock.java
Normal file
160
src/main/java/org/betterx/bclib/blocks/BaseVineBlock.java
Normal file
|
@ -0,0 +1,160 @@
|
|||
package org.betterx.bclib.blocks;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.tags.BlockTags;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.enchantment.EnchantmentHelper;
|
||||
import net.minecraft.world.item.enchantment.Enchantments;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
import net.minecraft.world.level.LevelReader;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.BonemealableBlock;
|
||||
import net.minecraft.world.level.block.SoundType;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.StateDefinition;
|
||||
import net.minecraft.world.level.block.state.properties.EnumProperty;
|
||||
import net.minecraft.world.level.material.Material;
|
||||
import net.minecraft.world.level.storage.loot.LootContext;
|
||||
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
|
||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import org.betterx.bclib.blocks.BlockProperties.TripleShape;
|
||||
import org.betterx.bclib.client.render.BCLRenderLayer;
|
||||
import org.betterx.bclib.interfaces.RenderLayerProvider;
|
||||
import org.betterx.bclib.items.tool.BaseShearsItem;
|
||||
import org.betterx.bclib.util.BlocksHelper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class BaseVineBlock extends BaseBlockNotFull implements RenderLayerProvider, BonemealableBlock {
|
||||
public static final EnumProperty<TripleShape> SHAPE = BlockProperties.TRIPLE_SHAPE;
|
||||
private static final VoxelShape VOXEL_SHAPE = box(2, 0, 2, 14, 16, 14);
|
||||
|
||||
public BaseVineBlock() {
|
||||
this(0, false);
|
||||
}
|
||||
|
||||
public BaseVineBlock(int light) {
|
||||
this(light, false);
|
||||
}
|
||||
|
||||
public BaseVineBlock(int light, boolean bottomOnly) {
|
||||
this(light, bottomOnly, p -> p);
|
||||
}
|
||||
|
||||
public BaseVineBlock(int light, boolean bottomOnly, Function<Properties, Properties> propMod) {
|
||||
this(
|
||||
propMod.apply(FabricBlockSettings
|
||||
.of(Material.PLANT)
|
||||
.sound(SoundType.GRASS)
|
||||
.lightLevel((state) -> bottomOnly ? state.getValue(SHAPE) == TripleShape.BOTTOM
|
||||
? light
|
||||
: 0 : light)
|
||||
.noCollission()
|
||||
.offsetType(BlockBehaviour.OffsetType.XZ))
|
||||
);
|
||||
}
|
||||
|
||||
public BaseVineBlock(BlockBehaviour.Properties properties) {
|
||||
super(properties.offsetType(BlockBehaviour.OffsetType.XZ));
|
||||
this.registerDefaultState(this.stateDefinition.any().setValue(SHAPE, TripleShape.BOTTOM));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> stateManager) {
|
||||
stateManager.add(SHAPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) {
|
||||
Vec3 vec3d = state.getOffset(view, pos);
|
||||
return VOXEL_SHAPE.move(vec3d.x, vec3d.y, vec3d.z);
|
||||
}
|
||||
|
||||
public boolean canGenerate(BlockState state, LevelReader world, BlockPos pos) {
|
||||
return isSupport(state, world, pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) {
|
||||
return isSupport(state, world, pos);
|
||||
}
|
||||
|
||||
protected boolean isSupport(BlockState state, LevelReader world, BlockPos pos) {
|
||||
BlockState up = world.getBlockState(pos.above());
|
||||
return up.is(this) || up.is(BlockTags.LEAVES) || canSupportCenter(world, pos.above(), Direction.DOWN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState updateShape(BlockState state,
|
||||
Direction facing,
|
||||
BlockState neighborState,
|
||||
LevelAccessor world,
|
||||
BlockPos pos,
|
||||
BlockPos neighborPos) {
|
||||
if (!canSurvive(state, world, pos)) {
|
||||
return Blocks.AIR.defaultBlockState();
|
||||
} else {
|
||||
if (world.getBlockState(pos.below()).getBlock() != this) return state.setValue(SHAPE, TripleShape.BOTTOM);
|
||||
else if (world.getBlockState(pos.above()).getBlock() != this) return state.setValue(SHAPE, TripleShape.TOP);
|
||||
return state.setValue(SHAPE, TripleShape.MIDDLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
|
||||
ItemStack tool = builder.getParameter(LootContextParams.TOOL);
|
||||
if (tool != null && BaseShearsItem.isShear(tool) || EnchantmentHelper.getItemEnchantmentLevel(
|
||||
Enchantments.SILK_TOUCH,
|
||||
tool
|
||||
) > 0) {
|
||||
return Lists.newArrayList(new ItemStack(this));
|
||||
} else {
|
||||
return Lists.newArrayList();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BCLRenderLayer getRenderLayer() {
|
||||
return BCLRenderLayer.CUTOUT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidBonemealTarget(BlockGetter world, BlockPos pos, BlockState state, boolean isClient) {
|
||||
while (world.getBlockState(pos).getBlock() == this) {
|
||||
pos = pos.below();
|
||||
}
|
||||
return world.getBlockState(pos).isAir();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBonemealSuccess(Level level, RandomSource random, BlockPos pos, BlockState state) {
|
||||
while (level.getBlockState(pos).getBlock() == this) {
|
||||
pos = pos.below();
|
||||
}
|
||||
return level.isEmptyBlock(pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
|
||||
while (level.getBlockState(pos).getBlock() == this) {
|
||||
pos = pos.below();
|
||||
}
|
||||
level.setBlockAndUpdate(pos, defaultBlockState());
|
||||
BlocksHelper.setWithoutUpdate(level, pos, defaultBlockState());
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue