From 66ddcf6947b08b83fdda36b6db6c9ae1f300621f Mon Sep 17 00:00:00 2001 From: Nyfaria DragonMoose Date: Tue, 9 Dec 2025 07:37:20 -0600 Subject: [PATCH 1/9] removal of onlyin and start on fabric registering of species items/blocks --- build.gradle | 15 +++++ .../block/leaves/LeavesProperties.java | 10 +-- .../client/BlockColorMultipliers.java | 2 +- .../client/SoundInstanceHandler.java | 2 +- .../client/ThickBranchRingsSprite.java | 6 +- .../entity/animation/AnimationHandler.java | 4 +- .../animation/FalloverAnimationHandler.java | 4 +- .../animation/PhysicsAnimationHandler.java | 4 +- .../animation/VoidAnimationHandler.java | 2 +- .../dynamictrees/tree/family/Family.java | 6 +- .../META-INF/dynamictrees.accesswidener | 5 +- fabric/build.gradle | 1 + .../platform/FabricEventHelper.java | 8 ++- .../registry/FabricRegistryHandler.java | 10 ++- fabric/src/main/resources/fabric.mod.json | 66 +++++++++---------- gradle.properties | 1 + neoforge/build.gradle | 4 +- .../event/handler/ClientGameEventHandler.java | 1 - .../event/handler/ClientModEventHandler.java | 12 ++-- .../dynamictrees/model/QuadManipulator.java | 4 +- .../baked/BakedModelBlockPottedSapling.java | 4 +- .../baked/BasicBranchBlockBakedModel.java | 4 +- .../baked/BasicRootsBlockBakedModel.java | 4 +- .../baked/LargePalmLeavesBakedModel.java | 4 +- .../baked/MediumPalmLeavesBakedModel.java | 4 +- .../model/baked/PalmLeavesBakedModel.java | 4 +- .../baked/SmallPalmLeavesBakedModel.java | 4 +- .../baked/SurfaceRootBlockBakedModel.java | 4 +- .../baked/ThickBranchBlockBakedModel.java | 4 +- .../geometry/BranchBlockModelGeometry.java | 4 +- .../model/loader/PalmLeavesModelLoader.java | 4 +- .../model/loader/RootsBlockModelLoader.java | 4 +- .../loader/SurfaceRootBlockModelLoader.java | 4 +- .../loader/ThickBranchBlockModelLoader.java | 4 +- .../platform/NeoForgeMiscHelper.java | 4 +- 35 files changed, 110 insertions(+), 117 deletions(-) diff --git a/build.gradle b/build.gradle index d40685804..61fdb0717 100644 --- a/build.gradle +++ b/build.gradle @@ -3,4 +3,19 @@ plugins { id 'fabric-loom' version '1.9-SNAPSHOT' apply false // see https://projects.neoforged.net/neoforged/moddevgradle for new versions id 'net.neoforged.moddev' version '2.0.49-beta' apply false + id 'com.matyrobbrt.mc.registrationutils' version "${regutils_version}" +} +registrationUtils { + group "com.dtteam.${mod_id}.registration" + projects { + fabric { + type 'fabric' + } + neoforge { + type 'neoforge' + } + common { + type 'common' + } + } } \ No newline at end of file diff --git a/common/src/main/java/com/dtteam/dynamictrees/block/leaves/LeavesProperties.java b/common/src/main/java/com/dtteam/dynamictrees/block/leaves/LeavesProperties.java index 8252f3f7b..f90dcd6fc 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/block/leaves/LeavesProperties.java +++ b/common/src/main/java/com/dtteam/dynamictrees/block/leaves/LeavesProperties.java @@ -655,15 +655,15 @@ public void setColorString(String colorString) { this.colorString = colorString; } - //@OnlyIn(Dist.CLIENT) + // private BlockColor colorMultiplier; - //@OnlyIn(Dist.CLIENT) + // public int treeFallColorMultiplier(BlockState state, BlockAndTintGetter level, BlockPos pos) { return this.foliageColorMultiplier(state, level, pos); } - //@OnlyIn(Dist.CLIENT) + // public int foliageColorMultiplier(BlockState state, BlockAndTintGetter level, BlockPos pos) { if (colorMultiplier == null) { return 0x00FF00FF; //purple if broken @@ -671,7 +671,7 @@ public int foliageColorMultiplier(BlockState state, BlockAndTintGetter level, Bl return colorMultiplier.getColor(state, level, pos, -1); } -// @OnlyIn(Dist.CLIENT) +// private void processColor() { int color = -1; if (this.colorNumber != null) { @@ -700,7 +700,7 @@ private void processColor() { this.colorMultiplier = (s, w, p, t) -> c == -1 ? Minecraft.getInstance().getBlockColors().getColor(getPrimitiveLeaves(), w, p, 0) : c; } - //@OnlyIn(Dist.CLIENT) + // public static void postInitClient() { REGISTRY.getAll().forEach(LeavesProperties::processColor); } diff --git a/common/src/main/java/com/dtteam/dynamictrees/client/BlockColorMultipliers.java b/common/src/main/java/com/dtteam/dynamictrees/client/BlockColorMultipliers.java index 338ae2051..696381cc7 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/client/BlockColorMultipliers.java +++ b/common/src/main/java/com/dtteam/dynamictrees/client/BlockColorMultipliers.java @@ -9,7 +9,7 @@ import java.util.HashMap; import java.util.Map; -//@OnlyIn(Dist.CLIENT) +// public class BlockColorMultipliers { private static Map colorBase = new HashMap<>(); diff --git a/common/src/main/java/com/dtteam/dynamictrees/client/SoundInstanceHandler.java b/common/src/main/java/com/dtteam/dynamictrees/client/SoundInstanceHandler.java index 5ff0ee963..1c3ec1fdd 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/client/SoundInstanceHandler.java +++ b/common/src/main/java/com/dtteam/dynamictrees/client/SoundInstanceHandler.java @@ -12,7 +12,7 @@ import java.util.HashMap; import java.util.Map; -//@OnlyIn(Dist.CLIENT) +// public class SoundInstanceHandler { private static final Map instances = new HashMap<>(); diff --git a/common/src/main/java/com/dtteam/dynamictrees/client/ThickBranchRingsSprite.java b/common/src/main/java/com/dtteam/dynamictrees/client/ThickBranchRingsSprite.java index 7a141e13f..02c5ae83f 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/client/ThickBranchRingsSprite.java +++ b/common/src/main/java/com/dtteam/dynamictrees/client/ThickBranchRingsSprite.java @@ -5,10 +5,8 @@ import net.minecraft.client.resources.metadata.animation.FrameSize; import net.minecraft.core.Direction; import net.minecraft.resources.ResourceLocation; -import net.neoforged.api.distmarker.Dist; -import net.neoforged.api.distmarker.OnlyIn; -@OnlyIn(Dist.CLIENT) + public class ThickBranchRingsSprite extends SpriteContents { private static final int RESOLUTION = 16; private static final int LAYERS = 3; @@ -18,7 +16,7 @@ public class ThickBranchRingsSprite extends SpriteContents { }; public ThickBranchRingsSprite(ResourceLocation name, SpriteContents originalSprite){ - super(name, getFrameSize(originalSprite), processImage(originalSprite.originalImage), originalSprite.metadata); + super(name, getFrameSize(originalSprite), processImage(originalSprite.originalImage), originalSprite.metadata()); } private static FrameSize getFrameSize(SpriteContents sprite){ diff --git a/common/src/main/java/com/dtteam/dynamictrees/entity/animation/AnimationHandler.java b/common/src/main/java/com/dtteam/dynamictrees/entity/animation/AnimationHandler.java index fe97af290..25dfbebd2 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/entity/animation/AnimationHandler.java +++ b/common/src/main/java/com/dtteam/dynamictrees/entity/animation/AnimationHandler.java @@ -14,10 +14,10 @@ public interface AnimationHandler { boolean shouldDie(FallingTreeEntity entity); -// @OnlyIn(Dist.CLIENT) +// void renderTransform(FallingTreeEntity entity, float entityYaw, float partialTick, PoseStack poseStack); -// @OnlyIn(Dist.CLIENT) +// boolean shouldRender(FallingTreeEntity entity, double x, double y, double z); } \ No newline at end of file diff --git a/common/src/main/java/com/dtteam/dynamictrees/entity/animation/FalloverAnimationHandler.java b/common/src/main/java/com/dtteam/dynamictrees/entity/animation/FalloverAnimationHandler.java index 548373ead..f665b0b12 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/entity/animation/FalloverAnimationHandler.java +++ b/common/src/main/java/com/dtteam/dynamictrees/entity/animation/FalloverAnimationHandler.java @@ -383,7 +383,7 @@ public boolean shouldDie(FallingTreeEntity entity) { } @Override -// @OnlyIn(Dist.CLIENT) +// public void renderTransform(FallingTreeEntity entity, float entityYaw, float partialTick, PoseStack poseStack) { float yaw = Mth.wrapDegrees(MathUtils.angleDegreesInterpolate(entity.yRotO, entity.getYRot(), partialTick)); @@ -406,7 +406,7 @@ public void renderTransform(FallingTreeEntity entity, float entityYaw, float par } @Override -// @OnlyIn(Dist.CLIENT) +// public boolean shouldRender(FallingTreeEntity entity, double x, double y, double z) { return true; } diff --git a/common/src/main/java/com/dtteam/dynamictrees/entity/animation/PhysicsAnimationHandler.java b/common/src/main/java/com/dtteam/dynamictrees/entity/animation/PhysicsAnimationHandler.java index 678681ec3..3781f6b54 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/entity/animation/PhysicsAnimationHandler.java +++ b/common/src/main/java/com/dtteam/dynamictrees/entity/animation/PhysicsAnimationHandler.java @@ -161,7 +161,7 @@ public boolean shouldDie(FallingTreeEntity entity) { } @Override -// @OnlyIn(Dist.CLIENT) +// public void renderTransform(FallingTreeEntity entity, float entityYaw, float partialTick, PoseStack poseStack) { final float yaw = Mth.wrapDegrees(MathUtils.angleDegreesInterpolate(entity.yRotO, entity.getYRot(), partialTick)); final float pit = Mth.wrapDegrees(MathUtils.angleDegreesInterpolate(entity.xRotO, entity.getXRot(), partialTick)); @@ -174,7 +174,7 @@ public void renderTransform(FallingTreeEntity entity, float entityYaw, float par } @Override -// @OnlyIn(Dist.CLIENT) +// public boolean shouldRender(FallingTreeEntity entity, double x, double y, double z) { return true; } diff --git a/common/src/main/java/com/dtteam/dynamictrees/entity/animation/VoidAnimationHandler.java b/common/src/main/java/com/dtteam/dynamictrees/entity/animation/VoidAnimationHandler.java index d2a9551be..4d4389731 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/entity/animation/VoidAnimationHandler.java +++ b/common/src/main/java/com/dtteam/dynamictrees/entity/animation/VoidAnimationHandler.java @@ -32,7 +32,7 @@ public void handleMotion(FallingTreeEntity entity) {} public void dropPayload(FallingTreeEntity entity) {} @Override -// @OnlyIn(Dist.CLIENT) +// public boolean shouldRender(FallingTreeEntity entity, double x, double y, double z) { return false; } diff --git a/common/src/main/java/com/dtteam/dynamictrees/tree/family/Family.java b/common/src/main/java/com/dtteam/dynamictrees/tree/family/Family.java index 251a8cd54..b7cd7ad1b 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/tree/family/Family.java +++ b/common/src/main/java/com/dtteam/dynamictrees/tree/family/Family.java @@ -178,9 +178,9 @@ public Species getSpeciesForLocation(LevelAccessor level, BlockPos trunkPos) { protected float lootVolumeMultiplier = 1.0f; -// @OnlyIn(Dist.CLIENT) +// public int woodRingColor; // For rooty blocks -// @OnlyIn(Dist.CLIENT) +// public int woodBarkColor; // For rooty water /** @@ -432,7 +432,7 @@ public void setMaxBranchRadius(int maxBranchRadius) { this.maxBranchRadius = maxBranchRadius; } -// @OnlyIn(Dist.CLIENT) +// public int getRootColor(BlockState state, boolean getBark) { return getBark ? woodBarkColor : woodRingColor; } diff --git a/common/src/main/resources/META-INF/dynamictrees.accesswidener b/common/src/main/resources/META-INF/dynamictrees.accesswidener index 80910dc3b..735004fd5 100644 --- a/common/src/main/resources/META-INF/dynamictrees.accesswidener +++ b/common/src/main/resources/META-INF/dynamictrees.accesswidener @@ -17,4 +17,7 @@ accessible field net/minecraft/world/level/levelgen/feature/stateproviders/Weigh accessible field net/minecraft/commands/synchronization/ArgumentTypeInfos BY_CLASS Ljava/util/Map; -accessible class net/minecraft/world/inventory/BrewingStandMenu$PotionSlot \ No newline at end of file +accessible class net/minecraft/world/inventory/BrewingStandMenu$PotionSlot +accessible class net/minecraft/data/tags/IntrinsicHolderTagsProvider$IntrinsicTagAppender +accessible method net/minecraft/client/renderer/texture/SpriteContents metadata ()Lnet/minecraft/server/packs/resources/ResourceMetadata; +accessible field net/minecraft/client/renderer/texture/SpriteContents originalImage Lcom/mojang/blaze3d/platform/NativeImage; diff --git a/fabric/build.gradle b/fabric/build.gradle index 6f3f11f93..8ec703042 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -11,6 +11,7 @@ dependencies { } modImplementation "net.fabricmc:fabric-loader:${fabric_loader_version}" modImplementation "net.fabricmc.fabric-api:fabric-api:${fabric_version}" + } loom { diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricEventHelper.java b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricEventHelper.java index 172b231b7..cfe6725eb 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricEventHelper.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricEventHelper.java @@ -32,10 +32,16 @@ public > void postTypedRegistryEvent(TypedRegistry } @Override - public void postAddResourceLoadersEvent(TreeResourceManager resourceManager) { + public void postAddResourceLoadersEventPre(TreeResourceManager resourceManager) { } + @Override + public void postAddResourceLoadersEventPost(TreeResourceManager resourceManager) { + + } + + @Override public void postJsonDeserializerRegistryEvent() { diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/registry/FabricRegistryHandler.java b/fabric/src/main/java/com/dtteam/dynamictrees/registry/FabricRegistryHandler.java index 40bd52824..b15fad23d 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/registry/FabricRegistryHandler.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/registry/FabricRegistryHandler.java @@ -1,6 +1,10 @@ package com.dtteam.dynamictrees.registry; -import com.dtteam.dynamictrees.api.registry.RegistryHandler; +import com.dtteam.dynamictrees.api.registry.*; +import net.minecraft.core.*; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.*; +import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.Item; import net.minecraft.world.level.block.Block; @@ -70,12 +74,12 @@ private boolean warnIfInvalid(final String type, final ResourceLocation registry @Override public Supplier putBlock(ResourceLocation registryName, Supplier blockSup) { - return null; + return ()->(T)Registry.register(BuiltInRegistries.BLOCK, registryName, (Block)blockSup.get()); } @Override public Supplier putItem(ResourceLocation registryName, Supplier itemSup) { - return null; + return ()->(T)Registry.register(BuiltInRegistries.ITEM, registryName, (Item)itemSup.get()); } public static class RegisterEventHandler { diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index 94900879e..db402c50f 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -1,36 +1,36 @@ { - "schemaVersion": 1, - "id": "${mod_id}", - "version": "${version}", - "name": "${mod_name}", - "description": "${description}", - "authors": [ - "${mod_author}" - ], - "contact": { - "homepage": "https://fabricmc.net/", - "sources": "https://github.com/FabricMC/fabric-example-mod" - }, - "license": "${license}", - "icon": "${mod_id}.png", - "environment": "*", - "entrypoints": { - "main": [ - "com.dtteam.dynamictrees.DynamicTreesFabric" - ] - }, - "mixins": [ - "${mod_id}.mixins.json", - "${mod_id}.fabric.mixins.json" - ], - "depends": { - "fabricloader": ">=${fabric_loader_version}", - "fabric-api": "*", - "minecraft": "${minecraft_version}", - "java": ">=${java_version}" - }, - "suggests": { - "another-mod": "*" - } + "schemaVersion": 1, + "id": "${mod_id}", + "version": "${version}", + "name": "${mod_name}", + "description": "${description}", + "authors": [ + "${mod_author}" + ], + "contact": { + "homepage": "https://fabricmc.net/", + "sources": "https://github.com/FabricMC/fabric-example-mod" + }, + "license": "${license}", + "icon": "${mod_id}.png", + "environment": "*", + "entrypoints": { + "main": [ + "com.dtteam.dynamictrees.DynamicTreesFabric" + ] + }, + "mixins": [ + "${mod_id}.mixins.json", + "${mod_id}.fabric.mixins.json" + ], + "depends": { + "fabricloader": ">=${fabric_loader_version}", + "fabric-api": "*", + "minecraft": "${minecraft_version}", + "java": ">=${java_version}" + }, + "suggests": { + "another-mod": "*" + } } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index f98ed3ec5..185f9e04e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -39,3 +39,4 @@ neoforge_loader_version_range=[4,) # Gradle org.gradle.jvmargs=-Xmx3G org.gradle.daemon=false +regutils_version=1.21.0-0.2.2 diff --git a/neoforge/build.gradle b/neoforge/build.gradle index 35cb6632f..a7498e654 100644 --- a/neoforge/build.gradle +++ b/neoforge/build.gradle @@ -106,13 +106,13 @@ publishMods { curseforge { projectId = "252818" projectSlug = "dynamictrees" // Required for discord webhook - accessToken = curseApiKey ?: System.getenv("CURSEFORGE_API_KEY") +// accessToken = curseApiKey ?: System.getenv("CURSEFORGE_API_KEY") minecraftVersions.add("1.21.1") optional("dynamictreesplus") } modrinth { projectId = "vdjF5PL5" - accessToken = modrinthToken ?: System.getenv("MODRINTH_TOKEN") +// accessToken = modrinthToken ?: System.getenv("MODRINTH_TOKEN") minecraftVersions.add("1.21.1") optional("qaO9Dqpu") } diff --git a/neoforge/src/main/java/com/dtteam/dynamictrees/event/handler/ClientGameEventHandler.java b/neoforge/src/main/java/com/dtteam/dynamictrees/event/handler/ClientGameEventHandler.java index 080301032..81c758e26 100644 --- a/neoforge/src/main/java/com/dtteam/dynamictrees/event/handler/ClientGameEventHandler.java +++ b/neoforge/src/main/java/com/dtteam/dynamictrees/event/handler/ClientGameEventHandler.java @@ -10,7 +10,6 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; -import net.neoforged.api.distmarker.Dist; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.neoforge.event.entity.player.ItemTooltipEvent; diff --git a/neoforge/src/main/java/com/dtteam/dynamictrees/event/handler/ClientModEventHandler.java b/neoforge/src/main/java/com/dtteam/dynamictrees/event/handler/ClientModEventHandler.java index 70466e9d2..5c976973b 100644 --- a/neoforge/src/main/java/com/dtteam/dynamictrees/event/handler/ClientModEventHandler.java +++ b/neoforge/src/main/java/com/dtteam/dynamictrees/event/handler/ClientModEventHandler.java @@ -32,8 +32,6 @@ import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.FoliageColor; import net.minecraft.world.level.block.state.BlockState; -import net.neoforged.api.distmarker.Dist; -import net.neoforged.api.distmarker.OnlyIn; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.neoforge.client.event.EntityRenderersEvent; @@ -53,7 +51,7 @@ public class ClientModEventHandler { // COLOR HANDLING /////////////////////////////////////////// - @OnlyIn(Dist.CLIENT) + public static void discoverWoodColors() { final Function bakedTextureGetter = Minecraft.getInstance() @@ -72,7 +70,7 @@ public static void discoverWoodColors() { } } - @OnlyIn(Dist.CLIENT) + private static int getFaceColor(BlockState state, Direction face, Function textureGetter) { final BakedModel model = Minecraft.getInstance().getBlockRenderer().getBlockModel(state); List quads = model.getQuads(state, face, RandomSource.create(), ModelData.EMPTY, null); @@ -94,14 +92,14 @@ private static int getFaceColor(BlockState state, Direction face, Function FoliageColor.getBirchColor()); BlockColorMultipliers.register("spruce", (state, level, pos, tintIndex) -> FoliageColor.getEvergreenColor()); } @SubscribeEvent - @OnlyIn(Dist.CLIENT) + public static void registerItemColorHandlersEvent(RegisterColorHandlersEvent.Item event){ // Register Potion Colorizer event.register(DTRegistries.DENDRO_POTION.get()::getColor, DTRegistries.DENDRO_POTION.get()); @@ -110,7 +108,7 @@ public static void registerItemColorHandlersEvent(RegisterColorHandlersEvent.Ite } @SubscribeEvent - @OnlyIn(Dist.CLIENT) + public static void registerBlockColorHandlersEvent(RegisterColorHandlersEvent.Block event){ final int white = 0xFFFFFFFF; diff --git a/neoforge/src/main/java/com/dtteam/dynamictrees/model/QuadManipulator.java b/neoforge/src/main/java/com/dtteam/dynamictrees/model/QuadManipulator.java index c17ddb70a..69fbf3f87 100644 --- a/neoforge/src/main/java/com/dtteam/dynamictrees/model/QuadManipulator.java +++ b/neoforge/src/main/java/com/dtteam/dynamictrees/model/QuadManipulator.java @@ -12,8 +12,6 @@ import net.minecraft.util.RandomSource; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.Vec3; -import net.neoforged.api.distmarker.Dist; -import net.neoforged.api.distmarker.OnlyIn; import net.neoforged.neoforge.client.model.data.ModelData; import org.jetbrains.annotations.Nullable; @@ -22,7 +20,7 @@ import java.util.Optional; import java.util.function.Function; -@OnlyIn(Dist.CLIENT) + public class QuadManipulator { public static final Direction[] everyFace = {Direction.DOWN, Direction.UP, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST, null}; diff --git a/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/BakedModelBlockPottedSapling.java b/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/BakedModelBlockPottedSapling.java index 674af24d2..e78c06131 100644 --- a/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/BakedModelBlockPottedSapling.java +++ b/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/BakedModelBlockPottedSapling.java @@ -14,8 +14,6 @@ import net.minecraft.util.RandomSource; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.Vec3; -import net.neoforged.api.distmarker.Dist; -import net.neoforged.api.distmarker.OnlyIn; import net.neoforged.neoforge.client.ChunkRenderTypeSet; import net.neoforged.neoforge.client.model.IDynamicBakedModel; import net.neoforged.neoforge.client.model.data.ModelData; @@ -27,7 +25,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -@OnlyIn(Dist.CLIENT) + public class BakedModelBlockPottedSapling implements IDynamicBakedModel { protected BakedModel basePotModel; diff --git a/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/BasicBranchBlockBakedModel.java b/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/BasicBranchBlockBakedModel.java index 295117303..1d988f3cc 100644 --- a/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/BasicBranchBlockBakedModel.java +++ b/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/BasicBranchBlockBakedModel.java @@ -20,8 +20,6 @@ import net.minecraft.world.inventory.InventoryMenu; import net.minecraft.world.level.BlockAndTintGetter; import net.minecraft.world.level.block.state.BlockState; -import net.neoforged.api.distmarker.Dist; -import net.neoforged.api.distmarker.OnlyIn; import net.neoforged.neoforge.client.ChunkRenderTypeSet; import net.neoforged.neoforge.client.NamedRenderTypeManager; import net.neoforged.neoforge.client.model.IDynamicBakedModel; @@ -36,7 +34,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; -@OnlyIn(Dist.CLIENT) + public class BasicBranchBlockBakedModel implements IDynamicBakedModel { protected final BlockModel blockModel; diff --git a/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/BasicRootsBlockBakedModel.java b/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/BasicRootsBlockBakedModel.java index 814528feb..e2a8b949f 100644 --- a/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/BasicRootsBlockBakedModel.java +++ b/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/BasicRootsBlockBakedModel.java @@ -18,8 +18,6 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.util.RandomSource; import net.minecraft.world.level.block.state.BlockState; -import net.neoforged.api.distmarker.Dist; -import net.neoforged.api.distmarker.OnlyIn; import net.neoforged.neoforge.client.model.IModelBuilder; import net.neoforged.neoforge.client.model.data.ModelData; import net.neoforged.neoforge.client.model.geometry.IGeometryBakingContext; @@ -30,7 +28,7 @@ import java.util.*; import java.util.function.Function; -@OnlyIn(Dist.CLIENT) + public class BasicRootsBlockBakedModel extends BasicBranchBlockBakedModel { private static final int MIN_RADIUS_FOR_CROSS = 4; diff --git a/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/LargePalmLeavesBakedModel.java b/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/LargePalmLeavesBakedModel.java index 0fdfba2aa..380b4fa38 100644 --- a/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/LargePalmLeavesBakedModel.java +++ b/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/LargePalmLeavesBakedModel.java @@ -9,14 +9,12 @@ import net.minecraft.client.resources.model.Material; import net.minecraft.client.resources.model.SimpleBakedModel; import net.minecraft.resources.ResourceLocation; -import net.neoforged.api.distmarker.Dist; -import net.neoforged.api.distmarker.OnlyIn; import java.util.ArrayList; import java.util.List; import java.util.function.Function; -@OnlyIn(Dist.CLIENT) + public class LargePalmLeavesBakedModel extends PalmLeavesBakedModel { public static List INSTANCES = new ArrayList<>(); diff --git a/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/MediumPalmLeavesBakedModel.java b/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/MediumPalmLeavesBakedModel.java index fa31545db..ebc84e5cb 100644 --- a/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/MediumPalmLeavesBakedModel.java +++ b/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/MediumPalmLeavesBakedModel.java @@ -9,14 +9,12 @@ import net.minecraft.client.resources.model.Material; import net.minecraft.client.resources.model.SimpleBakedModel; import net.minecraft.resources.ResourceLocation; -import net.neoforged.api.distmarker.Dist; -import net.neoforged.api.distmarker.OnlyIn; import java.util.ArrayList; import java.util.List; import java.util.function.Function; -@OnlyIn(Dist.CLIENT) + public class MediumPalmLeavesBakedModel extends PalmLeavesBakedModel { public static List INSTANCES = new ArrayList<>(); diff --git a/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/PalmLeavesBakedModel.java b/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/PalmLeavesBakedModel.java index 9f4994e12..bec492253 100644 --- a/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/PalmLeavesBakedModel.java +++ b/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/PalmLeavesBakedModel.java @@ -14,8 +14,6 @@ import net.minecraft.util.RandomSource; import net.minecraft.world.inventory.InventoryMenu; import net.minecraft.world.level.block.state.BlockState; -import net.neoforged.api.distmarker.Dist; -import net.neoforged.api.distmarker.OnlyIn; import net.neoforged.neoforge.client.ChunkRenderTypeSet; import net.neoforged.neoforge.client.RenderTypeGroup; import net.neoforged.neoforge.client.model.IDynamicBakedModel; @@ -26,7 +24,7 @@ import java.util.*; import java.util.function.Function; -@OnlyIn(Dist.CLIENT) + public abstract class PalmLeavesBakedModel implements IDynamicBakedModel { protected RenderTypeGroup renderGroup = new RenderTypeGroup(RenderType.cutout(), RenderType.cutout()); diff --git a/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/SmallPalmLeavesBakedModel.java b/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/SmallPalmLeavesBakedModel.java index 43d5e40a1..a8a6dcc9a 100644 --- a/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/SmallPalmLeavesBakedModel.java +++ b/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/SmallPalmLeavesBakedModel.java @@ -9,14 +9,12 @@ import net.minecraft.client.resources.model.Material; import net.minecraft.client.resources.model.SimpleBakedModel; import net.minecraft.resources.ResourceLocation; -import net.neoforged.api.distmarker.Dist; -import net.neoforged.api.distmarker.OnlyIn; import java.util.ArrayList; import java.util.List; import java.util.function.Function; -@OnlyIn(Dist.CLIENT) + public class SmallPalmLeavesBakedModel extends PalmLeavesBakedModel { public static List INSTANCES = new ArrayList<>(); diff --git a/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/SurfaceRootBlockBakedModel.java b/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/SurfaceRootBlockBakedModel.java index 9dd53a933..61eba1557 100644 --- a/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/SurfaceRootBlockBakedModel.java +++ b/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/SurfaceRootBlockBakedModel.java @@ -20,8 +20,6 @@ import net.minecraft.world.level.BlockAndTintGetter; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.AABB; -import net.neoforged.api.distmarker.Dist; -import net.neoforged.api.distmarker.OnlyIn; import net.neoforged.neoforge.client.model.IDynamicBakedModel; import net.neoforged.neoforge.client.model.IModelBuilder; import net.neoforged.neoforge.client.model.data.ModelData; @@ -32,7 +30,7 @@ import java.util.*; import java.util.function.Function; -@OnlyIn(Dist.CLIENT) + public class SurfaceRootBlockBakedModel implements IDynamicBakedModel { private final BlockModel blockModel; diff --git a/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/ThickBranchBlockBakedModel.java b/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/ThickBranchBlockBakedModel.java index b4f7a4836..b5a0e71b9 100644 --- a/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/ThickBranchBlockBakedModel.java +++ b/neoforge/src/main/java/com/dtteam/dynamictrees/model/baked/ThickBranchBlockBakedModel.java @@ -27,8 +27,6 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; -import net.neoforged.api.distmarker.Dist; -import net.neoforged.api.distmarker.OnlyIn; import net.neoforged.neoforge.client.model.IModelBuilder; import net.neoforged.neoforge.client.model.data.ModelData; import net.neoforged.neoforge.client.model.geometry.IGeometryBakingContext; @@ -42,7 +40,7 @@ import java.util.Map; import java.util.function.Function; -@OnlyIn(Dist.CLIENT) + public class ThickBranchBlockBakedModel extends BasicBranchBlockBakedModel { private final BakedModel[] trunksBark = new BakedModel[16]; // The trunk will always feature bark on its sides. diff --git a/neoforge/src/main/java/com/dtteam/dynamictrees/model/geometry/BranchBlockModelGeometry.java b/neoforge/src/main/java/com/dtteam/dynamictrees/model/geometry/BranchBlockModelGeometry.java index 8491bd9c3..d4d7fb2d5 100644 --- a/neoforge/src/main/java/com/dtteam/dynamictrees/model/geometry/BranchBlockModelGeometry.java +++ b/neoforge/src/main/java/com/dtteam/dynamictrees/model/geometry/BranchBlockModelGeometry.java @@ -11,8 +11,6 @@ import net.minecraft.client.resources.model.ModelBaker; import net.minecraft.client.resources.model.ModelState; import net.minecraft.resources.ResourceLocation; -import net.neoforged.api.distmarker.Dist; -import net.neoforged.api.distmarker.OnlyIn; import net.neoforged.neoforge.client.model.geometry.IGeometryBakingContext; import net.neoforged.neoforge.client.model.geometry.IUnbakedGeometry; import org.jetbrains.annotations.Nullable; @@ -30,7 +28,7 @@ * * @author Harley O'Connor */ -@OnlyIn(Dist.CLIENT) + public class BranchBlockModelGeometry implements IUnbakedGeometry { protected final Set textures = new HashSet<>(); protected final ResourceLocation barkTextureLocation; diff --git a/neoforge/src/main/java/com/dtteam/dynamictrees/model/loader/PalmLeavesModelLoader.java b/neoforge/src/main/java/com/dtteam/dynamictrees/model/loader/PalmLeavesModelLoader.java index c99fa5cc1..8b944fa52 100644 --- a/neoforge/src/main/java/com/dtteam/dynamictrees/model/loader/PalmLeavesModelLoader.java +++ b/neoforge/src/main/java/com/dtteam/dynamictrees/model/loader/PalmLeavesModelLoader.java @@ -6,13 +6,11 @@ import net.minecraft.ResourceLocationException; import net.minecraft.client.renderer.texture.MissingTextureAtlasSprite; import net.minecraft.resources.ResourceLocation; -import net.neoforged.api.distmarker.Dist; -import net.neoforged.api.distmarker.OnlyIn; import net.neoforged.neoforge.client.model.geometry.IGeometryLoader; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -@OnlyIn(Dist.CLIENT) + public class PalmLeavesModelLoader implements IGeometryLoader { public static final Logger LOGGER = LogManager.getLogger(); diff --git a/neoforge/src/main/java/com/dtteam/dynamictrees/model/loader/RootsBlockModelLoader.java b/neoforge/src/main/java/com/dtteam/dynamictrees/model/loader/RootsBlockModelLoader.java index d16ed9b3f..ba0d58061 100644 --- a/neoforge/src/main/java/com/dtteam/dynamictrees/model/loader/RootsBlockModelLoader.java +++ b/neoforge/src/main/java/com/dtteam/dynamictrees/model/loader/RootsBlockModelLoader.java @@ -3,14 +3,12 @@ import com.dtteam.dynamictrees.model.geometry.BranchBlockModelGeometry; import com.dtteam.dynamictrees.model.geometry.RootsBlockModelGeometry; import net.minecraft.resources.ResourceLocation; -import net.neoforged.api.distmarker.Dist; -import net.neoforged.api.distmarker.OnlyIn; import org.jetbrains.annotations.Nullable; /** * @author Harley O'Connor */ -@OnlyIn(Dist.CLIENT) + public class RootsBlockModelLoader extends BranchBlockModelLoader { protected BranchBlockModelGeometry getModelGeometry(final ResourceLocation barkTextureLocation, diff --git a/neoforge/src/main/java/com/dtteam/dynamictrees/model/loader/SurfaceRootBlockModelLoader.java b/neoforge/src/main/java/com/dtteam/dynamictrees/model/loader/SurfaceRootBlockModelLoader.java index 6b69a6a12..85f61c8ab 100644 --- a/neoforge/src/main/java/com/dtteam/dynamictrees/model/loader/SurfaceRootBlockModelLoader.java +++ b/neoforge/src/main/java/com/dtteam/dynamictrees/model/loader/SurfaceRootBlockModelLoader.java @@ -4,13 +4,11 @@ import com.dtteam.dynamictrees.model.geometry.SurfaceRootBlockModelGeometry; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonObject; -import net.neoforged.api.distmarker.Dist; -import net.neoforged.api.distmarker.OnlyIn; /** * @author Harley O'Connor */ -@OnlyIn(Dist.CLIENT) + public class SurfaceRootBlockModelLoader extends BranchBlockModelLoader { @Override diff --git a/neoforge/src/main/java/com/dtteam/dynamictrees/model/loader/ThickBranchBlockModelLoader.java b/neoforge/src/main/java/com/dtteam/dynamictrees/model/loader/ThickBranchBlockModelLoader.java index 70d942153..a8234854d 100644 --- a/neoforge/src/main/java/com/dtteam/dynamictrees/model/loader/ThickBranchBlockModelLoader.java +++ b/neoforge/src/main/java/com/dtteam/dynamictrees/model/loader/ThickBranchBlockModelLoader.java @@ -2,14 +2,12 @@ import com.dtteam.dynamictrees.model.geometry.BranchBlockModelGeometry; import net.minecraft.resources.ResourceLocation; -import net.neoforged.api.distmarker.Dist; -import net.neoforged.api.distmarker.OnlyIn; import org.jetbrains.annotations.Nullable; /** * @author Harley O'Connor */ -@OnlyIn(Dist.CLIENT) + public class ThickBranchBlockModelLoader extends BranchBlockModelLoader { @Override diff --git a/neoforge/src/main/java/com/dtteam/dynamictrees/platform/NeoForgeMiscHelper.java b/neoforge/src/main/java/com/dtteam/dynamictrees/platform/NeoForgeMiscHelper.java index ea969a101..18f038b80 100644 --- a/neoforge/src/main/java/com/dtteam/dynamictrees/platform/NeoForgeMiscHelper.java +++ b/neoforge/src/main/java/com/dtteam/dynamictrees/platform/NeoForgeMiscHelper.java @@ -10,8 +10,6 @@ import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.server.MinecraftServer; import net.minecraft.world.level.Level; -import net.neoforged.api.distmarker.Dist; -import net.neoforged.api.distmarker.OnlyIn; import net.neoforged.neoforge.server.ServerLifecycleHooks; public class NeoForgeMiscHelper implements IMiscHelper { @@ -26,7 +24,7 @@ public int getPixelRGBA(TextureAtlasSprite sprite, int x, int y) { } } - @Override @OnlyIn(Dist.CLIENT) + @Override public FallingTreeEntityModel newFallingTreeEntityModel(FallingTreeEntity entity) { return new FallingTreeEntityModelNF(entity); } From c7315c9c44cb084e6bbd3ddad5b7060fa9ec96e6 Mon Sep 17 00:00:00 2001 From: Nyfaria DragonMoose Date: Mon, 5 Jan 2026 18:07:51 -0600 Subject: [PATCH 2/9] need dist for event handling. --- .../dynamictrees/event/handler/ClientGameEventHandler.java | 1 + .../dtteam/dynamictrees/event/handler/ClientModEventHandler.java | 1 + 2 files changed, 2 insertions(+) diff --git a/neoforge/src/main/java/com/dtteam/dynamictrees/event/handler/ClientGameEventHandler.java b/neoforge/src/main/java/com/dtteam/dynamictrees/event/handler/ClientGameEventHandler.java index 81c758e26..5fe050ffe 100644 --- a/neoforge/src/main/java/com/dtteam/dynamictrees/event/handler/ClientGameEventHandler.java +++ b/neoforge/src/main/java/com/dtteam/dynamictrees/event/handler/ClientGameEventHandler.java @@ -10,6 +10,7 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; +import net.neoforged.api.distmarker.*; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.neoforge.event.entity.player.ItemTooltipEvent; diff --git a/neoforge/src/main/java/com/dtteam/dynamictrees/event/handler/ClientModEventHandler.java b/neoforge/src/main/java/com/dtteam/dynamictrees/event/handler/ClientModEventHandler.java index 5c976973b..67bca1337 100644 --- a/neoforge/src/main/java/com/dtteam/dynamictrees/event/handler/ClientModEventHandler.java +++ b/neoforge/src/main/java/com/dtteam/dynamictrees/event/handler/ClientModEventHandler.java @@ -32,6 +32,7 @@ import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.FoliageColor; import net.minecraft.world.level.block.state.BlockState; +import net.neoforged.api.distmarker.*; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.neoforge.client.event.EntityRenderersEvent; From b1d292d81894f0c271c4750398b58d0bc0d99339 Mon Sep 17 00:00:00 2001 From: Nyfaria DragonMoose Date: Sat, 24 Jan 2026 11:25:51 -0600 Subject: [PATCH 3/9] almost ready. --- .../META-INF/dynamictrees.accesswidener | 1 + fabric/build.gradle | 41 +- .../dynamictrees/DynamicTreesFabric.java | 36 +- .../DynamicTreesFabricClient.java | 191 +++++++++ .../dtteam/dynamictrees/config/DTConfigs.java | 10 +- .../event/handler/CommonEventHandler.java | 40 +- .../event/handler/ModEventHandler.java | 175 ++++----- .../handler/VanillaSaplingEventHandler.java | 63 +++ .../dynamictrees/mixin/MixinBushBlock.java | 34 ++ .../mixin/MixinDynamicTreeFeature.java | 85 +++++ .../mixin/MixinMinecraftServer.java | 16 + .../dynamictrees/mixin/MixinSaplingBlock.java | 44 +++ .../model/BakedModelBlockPottedSapling.java | 30 ++ .../model/BranchBlockUnbakedModel.java | 59 +++ .../model/DTModelLoadingPlugin.java | 212 ++++++++++ .../model/FallingTreeEntityModelFabric.java | 234 ++++++++++++ .../baked/BasicBranchBlockBakedModel.java | 303 +++++++++++++++ .../baked/BasicRootsBlockBakedModel.java | 160 ++++++++ .../baked/SurfaceRootBlockBakedModel.java | 361 ++++++++++++++++++ .../baked/ThickBranchBlockBakedModel.java | 235 ++++++++++++ .../platform/FabricConfigHelper.java | 16 +- .../platform/FabricEventHelper.java | 19 +- .../platform/FabricInteractionHelper.java | 17 +- .../platform/FabricMiscHelper.java | 29 +- .../registry/FabricRegistryHandler.java | 22 +- .../treepack/FabricModFileContainer.java | 11 +- .../worldgen/FabricBiomeModifications.java | 121 ++++++ .../worldgen/holderset/DTBiomeHolderSet.java | 85 ++++- .../worldgen/holderset/DelayedHolderSet.java | 81 ++++ .../holderset/NameRegexMatchHolderSet.java | 18 + .../worldgen/holderset/OrHolderSet.java | 27 ++ .../holderset/RegexMatchHolderSet.java | 49 +++ .../holderset/StreamBackedHolderSet.java | 61 +++ .../holderset/TagsRegexMatchHolderSet.java | 18 + .../resources/dynamictrees.fabric.mixins.json | 9 +- fabric/src/main/resources/fabric.mod.json | 9 +- 36 files changed, 2776 insertions(+), 146 deletions(-) create mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricClient.java create mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/event/handler/VanillaSaplingEventHandler.java create mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinBushBlock.java create mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinDynamicTreeFeature.java create mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinMinecraftServer.java create mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinSaplingBlock.java create mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/model/BakedModelBlockPottedSapling.java create mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/model/BranchBlockUnbakedModel.java create mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/model/DTModelLoadingPlugin.java create mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/model/FallingTreeEntityModelFabric.java create mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/model/baked/BasicBranchBlockBakedModel.java create mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/model/baked/BasicRootsBlockBakedModel.java create mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/model/baked/SurfaceRootBlockBakedModel.java create mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/model/baked/ThickBranchBlockBakedModel.java create mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/worldgen/FabricBiomeModifications.java create mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/DelayedHolderSet.java create mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/NameRegexMatchHolderSet.java create mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/OrHolderSet.java create mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/RegexMatchHolderSet.java create mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/StreamBackedHolderSet.java create mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/TagsRegexMatchHolderSet.java diff --git a/common/src/main/resources/META-INF/dynamictrees.accesswidener b/common/src/main/resources/META-INF/dynamictrees.accesswidener index 735004fd5..636bbfda5 100644 --- a/common/src/main/resources/META-INF/dynamictrees.accesswidener +++ b/common/src/main/resources/META-INF/dynamictrees.accesswidener @@ -21,3 +21,4 @@ accessible class net/minecraft/world/inventory/BrewingStandMenu$PotionSlot accessible class net/minecraft/data/tags/IntrinsicHolderTagsProvider$IntrinsicTagAppender accessible method net/minecraft/client/renderer/texture/SpriteContents metadata ()Lnet/minecraft/server/packs/resources/ResourceMetadata; accessible field net/minecraft/client/renderer/texture/SpriteContents originalImage Lcom/mojang/blaze3d/platform/NativeImage; +accessible method net/minecraft/client/renderer/texture/atlas/SpriteSources register (Ljava/lang/String;Lcom/mojang/serialization/MapCodec;)Lnet/minecraft/client/renderer/texture/atlas/SpriteSourceType; \ No newline at end of file diff --git a/fabric/build.gradle b/fabric/build.gradle index 8ec703042..65525c80d 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -1,6 +1,9 @@ +import me.modmuss50.mpp.ReleaseType + plugins { id 'multiloader-loader' id 'fabric-loom' + id "me.modmuss50.mod-publish-plugin" version "0.8.4" } dependencies { @@ -35,6 +38,15 @@ loom { ideConfigGenerated(true) runDir('runs/server') } + datagen { + client() + setConfigName('Fabric Datagen') + ideConfigGenerated(true) + runDir('runs/datagen') + vmArg("-Dfabric-api.datagen") + vmArg("-Dfabric-api.datagen.output-dir=${project(':common').file('src/generated/resources')}") + vmArg("-Dfabric-api.datagen.modid=${mod_id}") + } } repositories { maven { @@ -58,4 +70,31 @@ loom { modImplementation "curse.maven:glitchcore-955399:5660741" modImplementation "curse.maven:serene-seasons-291874:5753502" } -} \ No newline at end of file +} + +sourceSets.main.resources { srcDir 'src/generated/resources' } + +def changelogFile = file("build/changelog.txt") + +publishMods { + file = remapJar.archiveFile + displayName = "${mod_name.replace(' ','')}-Fabric-${minecraft_version} ${project.version}" + if (changelogFile.exists()) { + changelog.set(changelogFile.getText()) + } + type = ReleaseType.of(versionType.toUpperCase()) + modLoaders.add("fabric") + modLoaders.add("quilt") + + curseforge { + projectId = "252818" + projectSlug = "dynamictrees" + minecraftVersions.add("1.21.1") + optional("dynamictreesplus") + } + modrinth { + projectId = "vdjF5PL5" + minecraftVersions.add("1.21.1") + optional("qaO9Dqpu") + } +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabric.java b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabric.java index d07645a04..feda648db 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabric.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabric.java @@ -1,23 +1,57 @@ package com.dtteam.dynamictrees; +import com.dtteam.dynamictrees.block.leaves.LeavesProperties; +import com.dtteam.dynamictrees.client.BlockColorMultipliers; import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.event.handler.CommonEventHandler; import com.dtteam.dynamictrees.event.handler.ModEventHandler; +import com.dtteam.dynamictrees.event.handler.VanillaSaplingEventHandler; +import com.dtteam.dynamictrees.platform.FabricMiscHelper; +import com.dtteam.dynamictrees.registry.FabricRegistryHandler; import com.dtteam.dynamictrees.registry.FabricRegistryLoader; +import com.dtteam.dynamictrees.worldgen.FabricBiomeModifications; +import net.fabricmc.api.EnvType; import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.loader.api.FabricLoader; public class DynamicTreesFabric implements ModInitializer { @Override public void onInitialize() { - DTConfigs.registerConfigs(); //Must be first + DTConfigs.registerConfigs(); + + FabricRegistryHandler.setup(DynamicTrees.MOD_ID); CommonEventHandler.RegisterEvents(); ModEventHandler.RegisterEvents(); + VanillaSaplingEventHandler.register(); DynamicTrees.init(); FabricRegistryLoader.setup(); + + DynamicTrees.commonSetup(); + + FabricBiomeModifications.register(); + + ServerLifecycleEvents.SERVER_STARTING.register(server -> { + VanillaSaplingEventHandler.updateEnabled(); + FabricMiscHelper.debugSpeciesRegistry(); + }); + + ServerLifecycleEvents.SERVER_STOPPING.register(server -> { + FabricMiscHelper.currentServer = null; + }); + + if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) { + ServerLifecycleEvents.SERVER_STARTED.register(server -> { + LeavesProperties.postInitClient(); + BlockColorMultipliers.cleanUp(); + DynamicTreesFabricClient.registerBlockColors(); + DynamicTreesFabricClient.discoverWoodColors(); + }); + } } } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricClient.java b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricClient.java new file mode 100644 index 000000000..0a855e83b --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricClient.java @@ -0,0 +1,191 @@ +package com.dtteam.dynamictrees; + +import com.dtteam.dynamictrees.api.worldgen.*; +import com.dtteam.dynamictrees.block.branch.*; +import com.dtteam.dynamictrees.block.leaves.*; +import com.dtteam.dynamictrees.block.sapling.*; +import com.dtteam.dynamictrees.block.soil.*; +import com.dtteam.dynamictrees.client.*; +import com.dtteam.dynamictrees.entity.render.*; +import com.dtteam.dynamictrees.item.*; +import com.dtteam.dynamictrees.model.*; +import com.dtteam.dynamictrees.registry.*; +import com.dtteam.dynamictrees.systems.season.*; +import com.dtteam.dynamictrees.tree.*; +import com.dtteam.dynamictrees.tree.family.*; +import com.dtteam.dynamictrees.tree.species.*; +import net.fabricmc.api.*; +import net.fabricmc.fabric.api.blockrenderlayer.v1.*; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.*; +import net.fabricmc.fabric.api.client.item.v1.*; +import net.fabricmc.fabric.api.client.model.loading.v1.*; +import net.fabricmc.fabric.api.client.rendering.v1.*; +import net.fabricmc.fabric.impl.client.rendering.*; +import net.minecraft.client.*; +import net.minecraft.client.renderer.*; +import net.minecraft.client.renderer.block.model.*; +import net.minecraft.client.renderer.texture.*; +import net.minecraft.client.resources.model.*; +import net.minecraft.core.*; +import net.minecraft.resources.*; +import net.minecraft.util.*; +import net.minecraft.world.entity.player.*; +import net.minecraft.world.inventory.*; +import net.minecraft.world.item.*; +import net.minecraft.world.level.*; +import net.minecraft.world.level.block.state.*; + +import java.util.*; +import java.util.function.*; +import java.util.stream.*; + +public class DynamicTreesFabricClient implements ClientModInitializer { + + @Override + public void onInitializeClient() { + AtlasSourceTypeRegistryImpl.register(ThickBranchRingsSource.ID, ThickBranchRingsSource.setType(ThickBranchRingsSource.CODEC)); + registerModelLoaders(); + registerEntityRenderers(); + registerColorHandlers(); + registerTooltipCallback(); + registerClientTick(); + } + + private void registerModelLoaders() { + ModelLoadingPlugin.register(new DTModelLoadingPlugin()); + } + + private void registerEntityRenderers() { + EntityRendererRegistry.register(DTRegistries.FALLING_TREE.get(), FallingTreeRenderer::new); + EntityRendererRegistry.register(DTRegistries.LINGERING_EFFECTOR.get(), LingeringEffectorRenderer::new); + } + + private void registerColorHandlers() { + BlockColorMultipliers.register("birch", (state, level, pos, tintIndex) -> FoliageColor.getBirchColor()); + BlockColorMultipliers.register("spruce", (state, level, pos, tintIndex) -> FoliageColor.getEvergreenColor()); + + ColorProviderRegistry.ITEM.register(DTRegistries.DENDRO_POTION.get()::getColor, DTRegistries.DENDRO_POTION.get()); + ColorProviderRegistry.ITEM.register(DTRegistries.STAFF.get()::getColor, DTRegistries.STAFF.get()); + } + + public static void registerBlockColors() { + final int white = 0xFFFFFFFF; + final int magenta = 0x00FF00FF; + final var blockColors = Minecraft.getInstance().getBlockColors(); + + for (SoilProperties soil : SoilProperties.REGISTRY) { + if (soil.getBlock().isEmpty()) continue; + SoilBlock roots = soil.getBlock().get(); + ColorProviderRegistry.BLOCK.register( + (state, level, pos, tintIndex) -> roots.colorMultiplier(blockColors, state, level, pos, tintIndex), + roots + ); + BlockRenderLayerMap.INSTANCE.putBlock(roots, RenderType.cutoutMipped()); + } + + for (Family family : Family.REGISTRY.getAll()) { + if (family instanceof UndergroundRootsFamily rootsFamily) { + rootsFamily.getRoots().ifPresent(roots -> + BlockRenderLayerMap.INSTANCE.putBlock(roots, RenderType.cutoutMipped()) + ); + } + } + + ColorProviderRegistry.BLOCK.register( + (state, level, pos, tintIndex) -> isValidPos(level, pos) && (state.getBlock() instanceof PottedSaplingBlock) + ? DTRegistries.POTTED_SAPLING.get().getSpecies(level, pos).saplingColorMultiplier(state, level, pos, tintIndex) : white, + DTRegistries.POTTED_SAPLING.get() + ); + + for (Species species : Species.REGISTRY) { + if (species.getSapling().isPresent()) { + ColorProviderRegistry.BLOCK.register( + (state, level, pos, tintIndex) -> isValidPos(level, pos) + ? species.saplingColorMultiplier(state, level, pos, tintIndex) : white, + species.getSapling().get() + ); + } + } + + for (DynamicLeavesBlock leaves : LeavesProperties.REGISTRY.getAll().stream() + .filter(lp -> lp.getDynamicLeavesBlock().isPresent()) + .map(lp -> lp.getDynamicLeavesBlock().get()) + .collect(Collectors.toSet())) { + ColorProviderRegistry.BLOCK.register( + (state, level, pos, tintIndex) -> isValidPos(level, pos) && TreeHelper.isLeaves(state.getBlock()) + ? ((DynamicLeavesBlock) state.getBlock()).getLeavesProperties().foliageColorMultiplier(state, level, pos) : magenta, + leaves + ); + } + } + + private static boolean isValidPos(BlockGetter level, BlockPos pos) { + return level != null && pos != null; + } + + private void registerTooltipCallback() { + ItemTooltipCallback.EVENT.register((stack, context, type, lines) -> { + Item item = stack.getItem(); + if (!(item instanceof Seed seed)) { + return; + } + + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + + LevelContext levelContext = LevelContext.create(player.level()); + Species species = seed.getSpecies(); + + if (SeasonHelper.getSeasonValue(levelContext, BlockPos.ZERO) == null || !species.isValid()) { + return; + } + + int flags = seed.getSpecies().getSeasonalTooltipFlags(levelContext); + Tooltips.applySeasonalTooltips(lines, flags); + }); + } + + private void registerClientTick() { + ClientTickEvents.START_WORLD_TICK.register(level -> { + SeasonHelper.updateTick(level, level.getDayTime()); + }); + } + + public static void discoverWoodColors() { + final Function bakedTextureGetter = Minecraft.getInstance() + .getTextureAtlas(InventoryMenu.BLOCK_ATLAS); + + for (Family family : Family.REGISTRY.getAll()) { + family.woodRingColor = 0xFFF1AE; + family.woodBarkColor = 0xB3A979; + if (family != Family.NULL_FAMILY) { + family.getPrimitiveLog().ifPresent(branch -> { + BlockState state = branch.defaultBlockState(); + family.woodRingColor = getFaceColor(state, Direction.DOWN, bakedTextureGetter); + family.woodBarkColor = getFaceColor(state, Direction.NORTH, bakedTextureGetter); + }); + } + } + } + + private static int getFaceColor(BlockState state, Direction face, Function textureGetter) { + final BakedModel model = Minecraft.getInstance().getBlockRenderer().getBlockModel(state); + List quads = model.getQuads(state, face, RandomSource.create()); + if (quads.isEmpty()) { + quads = model.getQuads(state, null, RandomSource.create()); + } + if (quads.isEmpty()) { + DynamicTrees.LOG.warn("Could not get color of {} side for {}! Branch needs to be handled manually!", face, state.getBlock()); + return 0; + } + TextureAtlasSprite sprite = quads.getFirst().getSprite(); + final TextureHelper.PixelBuffer pixelBuffer = new TextureHelper.PixelBuffer(sprite); + final int u = pixelBuffer.w / 16; + final TextureHelper.PixelBuffer center = new TextureHelper.PixelBuffer(u * 8, u * 8); + pixelBuffer.blit(center, u * -8, u * -8); + + return center.averageColor(); + } +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/config/DTConfigs.java b/fabric/src/main/java/com/dtteam/dynamictrees/config/DTConfigs.java index bc8924b9d..75383c64e 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/config/DTConfigs.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/config/DTConfigs.java @@ -24,8 +24,12 @@ private static void createConfigs() { configs.addSection("seeds"); createConfig("The rate at which seeds drop from leaves.", IConfigHelper.LEAVES_SEED_DROP_RATE, 1D, 0D, 64D); + createConfig("The minimum chance for seed dropping from leaves when a seasonal mod is installed. 0 = during the off season seeds never drop from leaves, 1 = seeds will drop at maximum rate during the entire year. Can be fractional.", + IConfigHelper.MIN_SEASONAL_LEAVES_SEED_DROP_RATE, 0.15, 0.0, 1.0); createConfig("The rate at which seeds voluntarily drop from branches", IConfigHelper.VOLUNTARY_SEED_DROP_RATE, 0.01, 0.0, 1.0); + createConfig("The minimum chance for seed dropping voluntarily when a seasonal mod is installed. 0 = during the off season seeds never drop voluntarily, 1 = seeds will drop at maximum rate during the entire year. Can be fractional.", + IConfigHelper.MIN_SEASONAL_VOLUNTARY_SEED_DROP_RATE, 0.0, 0.0, 1.0); createConfig("The rate at which seeds voluntarily plant themselves in their ideal biomes", IConfigHelper.SEED_PLANT_RATE, 1f / 6f, 0.0, 1.0); createConfig("Ticks before a seed in the world attempts to plant itself or despawn. 1200 = 1 minute", @@ -37,13 +41,13 @@ private static void createConfigs() { configs.addSection("trees"); createConfig("Factor that multiplies the rate at which trees grow. Use at own risk", - IConfigHelper.TREE_GROWTH_MULTIPLIER, 0.5f, 0, 16f); + IConfigHelper.TREE_GROWTH_MULTIPLIER, 0.5f, 0f, 16f); createConfig("Factor that multiplies the wood returned from harvesting a tree. You cheat.", IConfigHelper.TREE_HARVEST_MULTIPLIER, 1f, 0f, 128f); createConfig("Maximum harvesting hardness that can be calculated. Regardless of tree thickness.", IConfigHelper.MAX_TREE_HARDNESS, 20f, 1f, 200f); createConfig("A multiplier of tree hardness. Higher values make trees slower to chop, lower values makes them faster to chop.", - IConfigHelper.TREE_HARDNESS_MULTIPLIER, 1, (1/128f), 32f); + IConfigHelper.TREE_HARDNESS_MULTIPLIER, 1f, (1/128f), 32f); createConfig("If enabled then sticks will be dropped for partial logs", IConfigHelper.DROP_STICKS, true); createConfig("Scales the growth for the environment. 0.5f is nominal. 0.0 trees only grow in their native biome. 1.0 trees grow anywhere like they are in their native biome", @@ -112,6 +116,8 @@ private static void createConfigs() { configs.addSection("misc"); createConfig("If enabled, dirt bucket recipes will be automatically generated.", IConfigHelper.GENERATE_DIRT_BUCKET_RECIPES, true); + createConfig("If enabled, seeds for mega species can be crafted with four regular seeds.", + IConfigHelper.GENERATE_MEGA_SEED_RECIPE, false); createConfig("The base potion the Biochar Base is brewed from. Minecraft potions use 'awkward'. If you change this, don't forget to update the patchouli manual page too.", IConfigHelper.BIOCHAR_BREWING_BASE, "minecraft:thick"); diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/CommonEventHandler.java b/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/CommonEventHandler.java index 061a77a0e..c64b73681 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/CommonEventHandler.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/CommonEventHandler.java @@ -1,8 +1,11 @@ package com.dtteam.dynamictrees.event.handler; import com.dtteam.dynamictrees.DynamicTrees; +import com.dtteam.dynamictrees.block.branch.BranchBlock; +import com.dtteam.dynamictrees.block.branch.TrunkShellBlock; +import com.dtteam.dynamictrees.block.sapling.PottedSaplingBlock; +import com.dtteam.dynamictrees.block.soil.SoilBlock; import com.dtteam.dynamictrees.command.DTCommand; -import com.dtteam.dynamictrees.platform.FabricMiscHelper; import com.dtteam.dynamictrees.systems.FutureBreak; import com.dtteam.dynamictrees.systems.season.SeasonHelper; import com.dtteam.dynamictrees.treepack.Resources; @@ -13,10 +16,17 @@ import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents; +import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents; import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener; import net.fabricmc.fabric.api.resource.ResourceManagerHelper; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.PackType; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.util.profiling.ProfilerFiller; +import net.minecraft.world.level.block.Block; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; public class CommonEventHandler { @@ -28,16 +38,11 @@ public static void RegisterEvents(){ }); ServerWorldEvents.LOAD.register(((minecraftServer, serverLevel) -> { - FabricMiscHelper.currentServer = minecraftServer; BiomeDatabases.populateBlacklistFromConfig(); })); -// if (event.getLevel().isClientSide()) { -// ClientModEventHandler.discoverWoodColors(); -// } ServerWorldEvents.UNLOAD.register(((minecraftServer, serverLevel) -> { - FabricMiscHelper.currentServer = null; - DynamicTreeFeature.DISC_PROVIDER.unloadWorld(serverLevel);//clears the circles + DynamicTreeFeature.DISC_PROVIDER.unloadWorld(serverLevel); })); ClientTickEvents.START_WORLD_TICK.register((level)->{ @@ -52,6 +57,20 @@ public static void RegisterEvents(){ new DTCommand().registerDTCommand(commandDispatcher); })); + PlayerBlockBreakEvents.BEFORE.register((level, player, pos, state, blockEntity) -> { + Block block = state.getBlock(); + if (block instanceof BranchBlock branchBlock) { + return branchBlock.onDestroyedByPlayer(state, level, pos, player, true, level.getFluidState(pos)); + } else if (block instanceof TrunkShellBlock trunkShellBlock) { + return trunkShellBlock.onDestroyedByPlayer(state, level, pos, player, true, level.getFluidState(pos)); + } else if (block instanceof SoilBlock soilBlock) { + return soilBlock.onDestroyedByPlayer(state, level, pos, player, true, level.getFluidState(pos)); + } else if (block instanceof PottedSaplingBlock pottedSaplingBlock) { + return pottedSaplingBlock.onDestroyedByPlayer(state, level, pos, player, true, level.getFluidState(pos)); + } + return true; + }); + ResourceManagerHelper.get(PackType.SERVER_DATA).registerReloadListener(new FabricReloadListener()); } @@ -62,6 +81,13 @@ public FabricReloadListener() { super(null); } + @Override + public CompletableFuture reload(PreparationBarrier stage, ResourceManager resourceManager, + ProfilerFiller preparationsProfiler, ProfilerFiller reloadProfiler, + Executor backgroundExecutor, Executor gameExecutor) { + return super.reload(stage, resourceManager, preparationsProfiler, reloadProfiler, backgroundExecutor, gameExecutor); + } + @Override public ResourceLocation getFabricId() { return DynamicTrees.location(DynamicTrees.MOD_ID); diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/ModEventHandler.java b/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/ModEventHandler.java index e16a8a7e9..33d7f6141 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/ModEventHandler.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/ModEventHandler.java @@ -1,12 +1,37 @@ package com.dtteam.dynamictrees.event.handler; +import com.dtteam.dynamictrees.DynamicTrees; +import com.dtteam.dynamictrees.api.cell.CellKit; import com.dtteam.dynamictrees.api.registry.Registries; import com.dtteam.dynamictrees.api.registry.Registry; import com.dtteam.dynamictrees.api.registry.SimpleRegistry; import com.dtteam.dynamictrees.api.worldgen.FeatureCanceller; +import com.dtteam.dynamictrees.block.leaves.LeavesProperties; +import com.dtteam.dynamictrees.block.leaves.PalmLeavesProperties; +import com.dtteam.dynamictrees.block.leaves.ScruffyLeavesProperties; +import com.dtteam.dynamictrees.block.leaves.SolidLeavesProperties; +import com.dtteam.dynamictrees.block.leaves.WartProperties; +import com.dtteam.dynamictrees.block.soil.AerialRootsSoilProperties; +import com.dtteam.dynamictrees.block.soil.SoilProperties; +import com.dtteam.dynamictrees.block.soil.SpreadableSoilProperties; +import com.dtteam.dynamictrees.block.soil.WaterSoilProperties; import com.dtteam.dynamictrees.deserialization.JsonDeserializers; +import com.dtteam.dynamictrees.systems.cell.CellKits; +import com.dtteam.dynamictrees.systems.genfeature.GenFeature; +import com.dtteam.dynamictrees.systems.genfeature.GenFeatures; +import com.dtteam.dynamictrees.systems.growthlogic.GrowthLogicKit; +import com.dtteam.dynamictrees.systems.growthlogic.GrowthLogicKits; +import com.dtteam.dynamictrees.tree.family.Family; +import com.dtteam.dynamictrees.tree.family.NetherFungusFamily; +import com.dtteam.dynamictrees.tree.family.PalmFamily; +import com.dtteam.dynamictrees.tree.family.UndergroundRootsFamily; +import com.dtteam.dynamictrees.tree.species.NetherFungusSpecies; +import com.dtteam.dynamictrees.tree.species.PalmSpecies; +import com.dtteam.dynamictrees.tree.species.Species; +import com.dtteam.dynamictrees.tree.species.SwampSpecies; +import com.dtteam.dynamictrees.tree.species.UndergroundRootsSpecies; import com.dtteam.dynamictrees.treepack.Resources; -import net.fabricmc.fabric.api.event.registry.DynamicRegistrySetupCallback; +import com.dtteam.dynamictrees.worldgen.featurecancellation.FeatureCancellers; import java.util.List; import java.util.stream.Collectors; @@ -14,96 +39,76 @@ public class ModEventHandler { public static void RegisterEvents(){ + registerLeavesPropertiesTypes(); + registerFamilyTypes(); + registerSpeciesTypes(); + registerSoilPropertiesTypes(); + registerCellKits(); + registerGrowthLogicKits(); + registerGenFeatures(); + registerFeatureCancellers(); + + final List> registries = Registries.REGISTRIES.stream() + .filter(registry -> registry instanceof SimpleRegistry) + .map(registry -> (SimpleRegistry) registry) + .collect(Collectors.toList()); + + registries.forEach(SimpleRegistry::postRegistryEvent); + + Resources.setupTreesResourceManager(); + + JsonDeserializers.registerRegistryEntryGetters(); + JsonDeserializers.postRegistryEvent(); + + FeatureCanceller.REGISTRY.postRegistryEvent(); + FeatureCanceller.REGISTRY.lock(); -// @SubscribeEvent -// public static void registerLeavesPropertiesTypes(final TypeRegistryEvent event) { -// if (!event.isEntryOfType(LeavesProperties.class)) return; -// event.registerType(DynamicTrees.location("solid"), SolidLeavesProperties.TYPE); -// event.registerType(DynamicTrees.location("wart"), WartProperties.TYPE); -// event.registerType(DynamicTrees.location("palm"), PalmLeavesProperties.TYPE); -// event.registerType(DynamicTrees.location("scruffy"), ScruffyLeavesProperties.TYPE); -// } -// -// @SubscribeEvent -// public static void registerFamilyTypes(final TypeRegistryEvent event) { -// if (!event.isEntryOfType(Family.class)) return; -// event.registerType(DynamicTrees.location("nether_fungus"), NetherFungusFamily.TYPE); -// event.registerType(DynamicTrees.location("underground_roots"), UndergroundRootsFamily.TYPE); -// event.registerType(DynamicTrees.location("palm"), PalmFamily.TYPE); -// } -// -// @SubscribeEvent -// public static void registerSpeciesTypes(final TypeRegistryEvent event) { -// if (!event.isEntryOfType(Species.class)) return; -// event.registerType(DynamicTrees.location("nether_fungus"), NetherFungusSpecies.TYPE); -// event.registerType(DynamicTrees.location("swamp"), SwampSpecies.TYPE); -// event.registerType(DynamicTrees.location("palm"), PalmSpecies.TYPE); -// event.registerType(DynamicTrees.location("underground_roots"), UndergroundRootsSpecies.TYPE); -// } -// -// @SubscribeEvent -// public static void registerSoilPropertiesTypes(final TypeRegistryEvent event) { -// if (!event.isEntryOfType(SoilProperties.class)) return; -// event.registerType(DynamicTrees.location("water"), WaterSoilProperties.TYPE); -// event.registerType(DynamicTrees.location("spreadable"), SpreadableSoilProperties.TYPE); -// event.registerType(DynamicTrees.location("aerial_roots"), AerialRootsSoilProperties.TYPE); -// } -// -// /////////////////////////////////////////// -// // CUSTOM TREE LOGIC -// /////////////////////////////////////////// -// -// @SubscribeEvent -// public static void onCellKitRegistry(final RegistryEvent event) { -// if (!event.isEntryOfType(CellKit.class)) return; -// CellKits.register(event.getRegistry()); -// } -// -// @SubscribeEvent -// public static void onGrowthLogicKitRegistry(final RegistryEvent event) { -// if (!event.isEntryOfType(GrowthLogicKit.class)) return; -// GrowthLogicKits.register(event.getRegistry()); -// } -// -// @SubscribeEvent -// public static void onGenFeatureRegistry(final RegistryEvent event) { -// if (!event.isEntryOfType(GenFeature.class)) return; -// GenFeatures.register(event.getRegistry()); -// } -// -// @SubscribeEvent -// public static void onFeatureCancellerRegistry(final RegistryEvent event) { -// if (!event.isEntryOfType(FeatureCanceller.class)) return; -// FeatureCancellers.register(event.getRegistry()); -// } - - DynamicRegistrySetupCallback.EVENT.register((dynamicRegistryView -> { - final List> registries = Registries.REGISTRIES.stream() - .filter(registry -> registry instanceof SimpleRegistry) - .map(registry -> (SimpleRegistry) registry) - .collect(Collectors.toList()); - - // Post registry events. - registries.forEach(SimpleRegistry::postRegistryEvent); - - Resources.setupTreesResourceManager(); - - // Register Forge registry entry getters and add-on Json object getters. - JsonDeserializers.registerRegistryEntryGetters(); - JsonDeserializers.postRegistryEvent(); - - // Register feature cancellers. - FeatureCanceller.REGISTRY.postRegistryEvent(); - FeatureCanceller.REGISTRY.lock(); - })); - - //Register any registry entries from Json files. Resources.MANAGER.load(); - // Lock all the registries. Registries.REGISTRIES.stream() .filter(registry -> registry instanceof SimpleRegistry) .forEach(Registry::lock); + } + + private static void registerLeavesPropertiesTypes() { + LeavesProperties.REGISTRY.registerType(DynamicTrees.location("solid"), SolidLeavesProperties.TYPE); + LeavesProperties.REGISTRY.registerType(DynamicTrees.location("wart"), WartProperties.TYPE); + LeavesProperties.REGISTRY.registerType(DynamicTrees.location("palm"), PalmLeavesProperties.TYPE); + LeavesProperties.REGISTRY.registerType(DynamicTrees.location("scruffy"), ScruffyLeavesProperties.TYPE); + } + + private static void registerFamilyTypes() { + Family.REGISTRY.registerType(DynamicTrees.location("nether_fungus"), NetherFungusFamily.TYPE); + Family.REGISTRY.registerType(DynamicTrees.location("underground_roots"), UndergroundRootsFamily.TYPE); + Family.REGISTRY.registerType(DynamicTrees.location("palm"), PalmFamily.TYPE); + } + + private static void registerSpeciesTypes() { + Species.REGISTRY.registerType(DynamicTrees.location("nether_fungus"), NetherFungusSpecies.TYPE); + Species.REGISTRY.registerType(DynamicTrees.location("swamp"), SwampSpecies.TYPE); + Species.REGISTRY.registerType(DynamicTrees.location("palm"), PalmSpecies.TYPE); + Species.REGISTRY.registerType(DynamicTrees.location("underground_roots"), UndergroundRootsSpecies.TYPE); + } + + private static void registerSoilPropertiesTypes() { + SoilProperties.REGISTRY.registerType(DynamicTrees.location("water"), WaterSoilProperties.TYPE); + SoilProperties.REGISTRY.registerType(DynamicTrees.location("spreadable"), SpreadableSoilProperties.TYPE); + SoilProperties.REGISTRY.registerType(DynamicTrees.location("aerial_roots"), AerialRootsSoilProperties.TYPE); + } + + private static void registerCellKits() { + CellKits.register(CellKit.REGISTRY); + } + + private static void registerGrowthLogicKits() { + GrowthLogicKits.register(GrowthLogicKit.REGISTRY); + } + + private static void registerGenFeatures() { + GenFeatures.register(GenFeature.REGISTRY); + } + private static void registerFeatureCancellers() { + FeatureCancellers.register(FeatureCanceller.REGISTRY); } } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/VanillaSaplingEventHandler.java b/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/VanillaSaplingEventHandler.java new file mode 100644 index 000000000..5725fe8fc --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/VanillaSaplingEventHandler.java @@ -0,0 +1,63 @@ +package com.dtteam.dynamictrees.event.handler; + +import com.dtteam.dynamictrees.block.sapling.DynamicSaplingBlock; +import com.dtteam.dynamictrees.platform.Services; +import com.dtteam.dynamictrees.platform.services.IConfigHelper; +import com.dtteam.dynamictrees.tree.species.Species; +import net.fabricmc.fabric.api.event.player.UseBlockCallback; +import net.minecraft.core.BlockPos; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.phys.BlockHitResult; + +public class VanillaSaplingEventHandler { + + private static boolean isEnabled = false; + + public static void register() { + UseBlockCallback.EVENT.register(VanillaSaplingEventHandler::onUseBlock); + } + + public static void updateEnabled() { + isEnabled = Services.CONFIG.getBoolConfig(IConfigHelper.REPLACE_VANILLA_SAPLINGS); + } + + private static InteractionResult onUseBlock(Player player, Level level, InteractionHand hand, BlockHitResult hitResult) { + if (!isEnabled || level.isClientSide()) { + return InteractionResult.PASS; + } + + ItemStack stack = player.getItemInHand(hand); + if (stack.isEmpty() || !(stack.getItem() instanceof BlockItem blockItem)) { + return InteractionResult.PASS; + } + + Block block = blockItem.getBlock(); + if (!DynamicSaplingBlock.SAPLING_REPLACERS.containsKey(block)) { + return InteractionResult.PASS; + } + + BlockPos placePos = hitResult.getBlockPos().relative(hitResult.getDirection()); + + if (!level.getBlockState(placePos).canBeReplaced()) { + return InteractionResult.PASS; + } + + Species targetSpecies = DynamicSaplingBlock.SAPLING_REPLACERS.get(block); + Species species = targetSpecies.selfOrLocationOverride(level, placePos); + + if (species.plantSapling(level, placePos, targetSpecies != species)) { + if (!player.isCreative()) { + stack.shrink(1); + } + return InteractionResult.SUCCESS; + } + + return InteractionResult.PASS; + } +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinBushBlock.java b/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinBushBlock.java new file mode 100644 index 000000000..7447e03d0 --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinBushBlock.java @@ -0,0 +1,34 @@ +package com.dtteam.dynamictrees.mixin; + +import com.dtteam.dynamictrees.block.branch.BasicRootsBlock; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.BushBlock; +import net.minecraft.world.level.block.state.BlockState; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(BushBlock.class) +public class MixinBushBlock { + + @Shadow protected boolean mayPlaceOn(BlockState state, BlockGetter level, BlockPos pos){ return false; } + + @Inject(at = @At("HEAD"), cancellable = true, method = "canSurvive (Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/LevelReader;Lnet/minecraft/core/BlockPos;)Z") + private void canSurvive(BlockState state, LevelReader level, BlockPos pos, CallbackInfoReturnable cir) { + BlockPos blockpos = pos.below(); + BlockState belowBlockState = level.getBlockState(blockpos); + if (belowBlockState.getBlock() instanceof BasicRootsBlock roots){ + if (belowBlockState.getValue(BasicRootsBlock.LAYER) == BasicRootsBlock.Layer.COVERED){ + Block block = BasicRootsBlock.Layer.COVERED.getPrimitive(roots.getFamily()).orElse(null); + if (block == null) return; + cir.setReturnValue(mayPlaceOn(block.defaultBlockState(), level, blockpos)); + } + } + } + +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinDynamicTreeFeature.java b/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinDynamicTreeFeature.java new file mode 100644 index 000000000..e591be35a --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinDynamicTreeFeature.java @@ -0,0 +1,85 @@ +package com.dtteam.dynamictrees.mixin; + +import com.dtteam.dynamictrees.DynamicTrees; +import com.dtteam.dynamictrees.api.worldgen.BiomePropertySelectors; +import com.dtteam.dynamictrees.api.worldgen.RandomXOR; +import com.dtteam.dynamictrees.tree.species.Species; +import com.dtteam.dynamictrees.worldgen.BiomeDatabase; +import com.dtteam.dynamictrees.worldgen.feature.DynamicTreeFeature; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.Blocks; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.lang.reflect.Field; +import java.util.List; + +@Mixin(value = DynamicTreeFeature.class, remap = false) +public abstract class MixinDynamicTreeFeature { + + @Unique + private static boolean dynamictrees$loggedCherrySelector = false; + + @Unique + private static final RandomXOR dynamictrees$debugRandom = new RandomXOR(); + + @Inject(method = "getSpeciesSelector", at = @At("RETURN"), remap = false) + private void onGetSpeciesSelector(BiomeDatabase.EntryReader biomeEntry, CallbackInfoReturnable cir) { + if (dynamictrees$loggedCherrySelector) { + return; + } + if (biomeEntry != null && biomeEntry.getBiomeKey() != null) { + String biomeName = biomeEntry.getBiomeKey().location().toString(); + if (biomeName.contains("cherry")) { + dynamictrees$loggedCherrySelector = true; + BiomePropertySelectors.SpeciesSelector selector = cir.getReturnValue(); + DynamicTrees.LOG.info("Cherry biome species selector type: {} for biome {}", + selector.getClass().getSimpleName(), biomeName); + + dynamictrees$debugRandom.setXOR(BlockPos.ZERO); + try { + BiomePropertySelectors.SpeciesSelection selection = selector.getSpecies( + BlockPos.ZERO, Blocks.DIRT.defaultBlockState(), dynamictrees$debugRandom); + Species species = selection.getSpecies(); + DynamicTrees.LOG.info("Cherry biome would select species: {} (valid: {}, handled: {})", + species.getRegistryName(), species.isValid(), selection.isHandled()); + } catch (Exception e) { + DynamicTrees.LOG.error("Failed to test species selection for cherry biome", e); + } + + if (selector instanceof BiomePropertySelectors.RandomSpeciesSelector randomSelector) { + dynamictrees$logRandomSelectorContents(randomSelector); + } + } + } + } + + @Unique + private void dynamictrees$logRandomSelectorContents(BiomePropertySelectors.RandomSpeciesSelector selector) { + try { + Field decisionTableField = BiomePropertySelectors.RandomSpeciesSelector.class.getDeclaredField("decisionTable"); + decisionTableField.setAccessible(true); + List decisionTable = (List) decisionTableField.get(selector); + + DynamicTrees.LOG.info("RandomSpeciesSelector contains {} entries:", decisionTable.size()); + + for (Object entry : decisionTable) { + Field decisionField = entry.getClass().getDeclaredField("decision"); + Field weightField = entry.getClass().getDeclaredField("weight"); + decisionField.setAccessible(true); + weightField.setAccessible(true); + + BiomePropertySelectors.SpeciesSelection decision = (BiomePropertySelectors.SpeciesSelection) decisionField.get(entry); + int weight = (int) weightField.get(entry); + + DynamicTrees.LOG.info(" - Species: {}, Weight: {}, Handled: {}", + decision.getSpecies().getRegistryName(), weight, decision.isHandled()); + } + } catch (Exception e) { + DynamicTrees.LOG.error("Failed to log RandomSpeciesSelector contents", e); + } + } +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinMinecraftServer.java b/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinMinecraftServer.java new file mode 100644 index 000000000..44f4bcc99 --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinMinecraftServer.java @@ -0,0 +1,16 @@ +package com.dtteam.dynamictrees.mixin; + +import com.dtteam.dynamictrees.platform.FabricMiscHelper; +import net.minecraft.server.MinecraftServer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = MinecraftServer.class, priority = 500) +public abstract class MixinMinecraftServer { + @Inject(method = "", at = @At(value = "RETURN")) + private void onServerConstruction(CallbackInfo ci) { + FabricMiscHelper.currentServer = (MinecraftServer) (Object) this; + } +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinSaplingBlock.java b/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinSaplingBlock.java new file mode 100644 index 000000000..06fab9999 --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinSaplingBlock.java @@ -0,0 +1,44 @@ +package com.dtteam.dynamictrees.mixin; + +import com.dtteam.dynamictrees.block.sapling.DynamicSaplingBlock; +import com.dtteam.dynamictrees.platform.Services; +import com.dtteam.dynamictrees.platform.services.IConfigHelper; +import com.dtteam.dynamictrees.tree.species.Species; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.SaplingBlock; +import net.minecraft.world.level.block.state.BlockState; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(SaplingBlock.class) +public class MixinSaplingBlock { + + @Inject(method = "advanceTree", at = @At("HEAD"), cancellable = true) + private void onAdvanceTree(ServerLevel level, BlockPos pos, BlockState state, RandomSource random, CallbackInfo ci) { + if (!Services.CONFIG.getBoolConfig(IConfigHelper.REPLACE_VANILLA_SAPLINGS)) { + return; + } + + Block block = state.getBlock(); + if (!DynamicSaplingBlock.SAPLING_REPLACERS.containsKey(block)) { + return; + } + + Species species = DynamicSaplingBlock.SAPLING_REPLACERS.get(block) + .selfOrLocationOverride(level, pos); + + level.removeBlock(pos, false); + ci.cancel(); + + if (species.isValid()) { + if (DynamicSaplingBlock.canSaplingStay(level, species, pos)) { + species.transitionToTree(level, pos); + } + } + } +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/BakedModelBlockPottedSapling.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/BakedModelBlockPottedSapling.java new file mode 100644 index 000000000..38e47de31 --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/BakedModelBlockPottedSapling.java @@ -0,0 +1,30 @@ +package com.dtteam.dynamictrees.model; + +import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel; +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.core.Direction; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public class BakedModelBlockPottedSapling extends ForwardingBakedModel { + + public BakedModelBlockPottedSapling(BakedModel basePotModel) { + this.wrapped = basePotModel; + } + + @Override + public boolean isVanillaAdapter() { + return true; + } + + @Override + @NotNull + public List getQuads(BlockState state, Direction face, RandomSource random) { + return new ArrayList<>(wrapped.getQuads(state, face, random)); + } +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/BranchBlockUnbakedModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/BranchBlockUnbakedModel.java new file mode 100644 index 000000000..bd5c0c98c --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/BranchBlockUnbakedModel.java @@ -0,0 +1,59 @@ +package com.dtteam.dynamictrees.model; + +import com.dtteam.dynamictrees.model.baked.BasicBranchBlockBakedModel; +import com.dtteam.dynamictrees.model.baked.ThickBranchBlockBakedModel; +import com.dtteam.dynamictrees.tree.family.Family; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.client.resources.model.Material; +import net.minecraft.client.resources.model.ModelBaker; +import net.minecraft.client.resources.model.ModelState; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.inventory.InventoryMenu; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.Collections; +import java.util.function.Function; + +public class BranchBlockUnbakedModel implements UnbakedModel { + + protected final ResourceLocation barkTextureLocation; + protected final ResourceLocation ringsTextureLocation; + protected final ResourceLocation familyName; + protected final boolean forceThickness; + + public BranchBlockUnbakedModel(ResourceLocation barkTextureLocation, ResourceLocation ringsTextureLocation, @Nullable ResourceLocation familyName, boolean forceThickness) { + this.barkTextureLocation = barkTextureLocation; + this.ringsTextureLocation = ringsTextureLocation; + this.familyName = familyName; + this.forceThickness = forceThickness; + } + + @Override + public Collection getDependencies() { + return Collections.emptyList(); + } + + @Override + public void resolveParents(Function resolver) { + } + + @Override + public BakedModel bake(ModelBaker baker, Function spriteGetter, ModelState state) { + TextureAtlasSprite barkSprite = spriteGetter.apply(new Material(InventoryMenu.BLOCK_ATLAS, barkTextureLocation)); + TextureAtlasSprite ringsSprite = spriteGetter.apply(new Material(InventoryMenu.BLOCK_ATLAS, ringsTextureLocation)); + + Family family = familyName != null ? Family.REGISTRY.get(familyName) : null; + boolean useThickModel = forceThickness || (family != null && family.isThick()); + + if (useThickModel) { + ResourceLocation thickRingsLocation = ringsTextureLocation.withSuffix("_thick"); + TextureAtlasSprite thickRingsSprite = spriteGetter.apply(new Material(InventoryMenu.BLOCK_ATLAS, thickRingsLocation)); + return new ThickBranchBlockBakedModel(barkSprite, ringsSprite, thickRingsSprite); + } + + return new BasicBranchBlockBakedModel(barkSprite, ringsSprite); + } +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/DTModelLoadingPlugin.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/DTModelLoadingPlugin.java new file mode 100644 index 000000000..b8c83595e --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/DTModelLoadingPlugin.java @@ -0,0 +1,212 @@ +package com.dtteam.dynamictrees.model; + +import com.dtteam.dynamictrees.DynamicTrees; +import com.dtteam.dynamictrees.block.branch.BasicRootsBlock; +import com.dtteam.dynamictrees.block.branch.BranchBlock; +import com.dtteam.dynamictrees.block.branch.SurfaceRootBlock; +import com.dtteam.dynamictrees.model.baked.BasicBranchBlockBakedModel; +import com.dtteam.dynamictrees.model.baked.BasicRootsBlockBakedModel; +import com.dtteam.dynamictrees.model.baked.SurfaceRootBlockBakedModel; +import com.dtteam.dynamictrees.model.baked.ThickBranchBlockBakedModel; +import com.dtteam.dynamictrees.tree.family.Family; +import com.dtteam.dynamictrees.tree.family.UndergroundRootsFamily; +import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin; +import net.fabricmc.fabric.api.client.model.loading.v1.ModelModifier; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.client.resources.model.Material; +import net.minecraft.client.resources.model.ModelResourceLocation; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.inventory.InventoryMenu; +import net.minecraft.world.level.block.Block; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; + +public class DTModelLoadingPlugin implements ModelLoadingPlugin { + + public static final ResourceLocation POTTED_SAPLING_MODEL = DynamicTrees.location("potted_sapling"); + private static final Map BRANCH_MODEL_CACHE = new HashMap<>(); + private static final Map ROOT_MODEL_CACHE = new HashMap<>(); + private static final Map UNDERGROUND_ROOTS_MODEL_CACHE = new HashMap<>(); + private static boolean modelsInitialized = false; + + @Override + public void onInitializeModelLoader(Context pluginContext) { + modelsInitialized = false; + BRANCH_MODEL_CACHE.clear(); + ROOT_MODEL_CACHE.clear(); + UNDERGROUND_ROOTS_MODEL_CACHE.clear(); + pluginContext.modifyModelAfterBake().register(ModelModifier.WRAP_PHASE, this::modifyModelAfterBake); + } + + private void initBranchModels(Function spriteGetter) { + if (modelsInitialized) return; + modelsInitialized = true; + + for (Family family : Family.REGISTRY.getAll()) { + if (!family.isValid()) continue; + + family.getPrimitiveLog().ifPresent(primitiveLog -> { + ResourceLocation primitiveLogId = BuiltInRegistries.BLOCK.getKey(primitiveLog); + ResourceLocation barkTexture = ResourceLocation.fromNamespaceAndPath(primitiveLogId.getNamespace(), "block/" + primitiveLogId.getPath()); + ResourceLocation ringsTexture = barkTexture.withSuffix("_top"); + + AtomicReference barkRef = new AtomicReference<>(barkTexture); + AtomicReference ringsRef = new AtomicReference<>(ringsTexture); + + family.getTexturePath(Family.BRANCH).ifPresent(barkRef::set); + family.getTexturePath(Family.BRANCH_TOP).ifPresent(ringsRef::set); + + boolean isThick = family.isThick(); + + family.getBranch().ifPresent(branch -> { + ResourceLocation blockId = BuiltInRegistries.BLOCK.getKey(branch); + BakedModel model = createBranchModel(barkRef.get(), ringsRef.get(), isThick, spriteGetter); + BRANCH_MODEL_CACHE.put(blockId, model); + }); + }); + + family.getPrimitiveStrippedLog().ifPresent(strippedLog -> { + ResourceLocation strippedLogId = BuiltInRegistries.BLOCK.getKey(strippedLog); + ResourceLocation strippedBarkTexture = ResourceLocation.fromNamespaceAndPath(strippedLogId.getNamespace(), "block/" + strippedLogId.getPath()); + ResourceLocation strippedRingsTexture = strippedBarkTexture.withSuffix("_top"); + + AtomicReference barkRef = new AtomicReference<>(strippedBarkTexture); + AtomicReference ringsRef = new AtomicReference<>(strippedRingsTexture); + + family.getTexturePath(Family.STRIPPED_BRANCH).ifPresent(barkRef::set); + family.getTexturePath(Family.STRIPPED_BRANCH_TOP).ifPresent(ringsRef::set); + + boolean isThick = family.isThick(); + + family.getStrippedBranch().ifPresent(strippedBranch -> { + ResourceLocation blockId = BuiltInRegistries.BLOCK.getKey(strippedBranch); + BakedModel model = createBranchModel(barkRef.get(), ringsRef.get(), isThick, spriteGetter); + BRANCH_MODEL_CACHE.put(blockId, model); + }); + }); + + family.getSurfaceRoot().ifPresent(surfaceRoot -> { + family.getPrimitiveLog().ifPresent(primitiveLog -> { + ResourceLocation primitiveLogId = BuiltInRegistries.BLOCK.getKey(primitiveLog); + ResourceLocation barkTexture = ResourceLocation.fromNamespaceAndPath(primitiveLogId.getNamespace(), "block/" + primitiveLogId.getPath()); + AtomicReference barkRef = new AtomicReference<>(barkTexture); + family.getTexturePath(Family.BRANCH).ifPresent(barkRef::set); + + ResourceLocation blockId = BuiltInRegistries.BLOCK.getKey(surfaceRoot); + BakedModel model = createRootModel(barkRef.get(), spriteGetter); + ROOT_MODEL_CACHE.put(blockId, model); + }); + }); + + if (family instanceof UndergroundRootsFamily undergroundFamily) { + undergroundFamily.getRoots().ifPresent(roots -> { + ResourceLocation blockId = BuiltInRegistries.BLOCK.getKey(roots); + + undergroundFamily.getPrimitiveRoots().ifPresent(primitiveRoots -> { + ResourceLocation primitiveRootsId = BuiltInRegistries.BLOCK.getKey(primitiveRoots); + ResourceLocation barkTexture = ResourceLocation.fromNamespaceAndPath(primitiveRootsId.getNamespace(), "block/" + primitiveRootsId.getPath() + "_side"); + ResourceLocation ringsTexture = ResourceLocation.fromNamespaceAndPath(primitiveRootsId.getNamespace(), "block/" + primitiveRootsId.getPath() + "_top"); + + AtomicReference barkRef = new AtomicReference<>(barkTexture); + AtomicReference ringsRef = new AtomicReference<>(ringsTexture); + + family.getTexturePath(Family.ROOTS_SIDE).ifPresent(barkRef::set); + family.getTexturePath(Family.ROOTS_TOP).ifPresent(ringsRef::set); + + BakedModel model = createRootsBlockModel(barkRef.get(), ringsRef.get(), spriteGetter); + UNDERGROUND_ROOTS_MODEL_CACHE.put(blockId.withSuffix("_exposed"), model); + }); + + undergroundFamily.getPrimitiveFilledRoots().ifPresent(primitiveFilledRoots -> { + ResourceLocation primitiveFilledRootsId = BuiltInRegistries.BLOCK.getKey(primitiveFilledRoots); + ResourceLocation barkTexture = ResourceLocation.fromNamespaceAndPath(primitiveFilledRootsId.getNamespace(), "block/" + primitiveFilledRootsId.getPath() + "_side"); + ResourceLocation ringsTexture = ResourceLocation.fromNamespaceAndPath(primitiveFilledRootsId.getNamespace(), "block/" + primitiveFilledRootsId.getPath() + "_top"); + + BakedModel model = createRootsBlockModel(barkTexture, ringsTexture, spriteGetter); + UNDERGROUND_ROOTS_MODEL_CACHE.put(blockId.withSuffix("_filled"), model); + }); + }); + } + } + } + + private BakedModel createBranchModel(ResourceLocation barkTexture, ResourceLocation ringsTexture, boolean isThick, Function spriteGetter) { + TextureAtlasSprite barkSprite = spriteGetter.apply(new Material(InventoryMenu.BLOCK_ATLAS, barkTexture)); + TextureAtlasSprite ringsSprite = spriteGetter.apply(new Material(InventoryMenu.BLOCK_ATLAS, ringsTexture)); + + if (isThick) { + ResourceLocation thickRingsTexture = ringsTexture.withSuffix("_thick"); + TextureAtlasSprite thickRingsSprite = spriteGetter.apply(new Material(InventoryMenu.BLOCK_ATLAS, thickRingsTexture)); + return new ThickBranchBlockBakedModel(barkSprite, ringsSprite, thickRingsSprite); + } + + return new BasicBranchBlockBakedModel(barkSprite, ringsSprite); + } + + private BakedModel createRootModel(ResourceLocation barkTexture, Function spriteGetter) { + TextureAtlasSprite barkSprite = spriteGetter.apply(new Material(InventoryMenu.BLOCK_ATLAS, barkTexture)); + return new SurfaceRootBlockBakedModel(barkSprite); + } + + private BakedModel createRootsBlockModel(ResourceLocation barkTexture, ResourceLocation ringsTexture, Function spriteGetter) { + TextureAtlasSprite barkSprite = spriteGetter.apply(new Material(InventoryMenu.BLOCK_ATLAS, barkTexture)); + TextureAtlasSprite ringsSprite = spriteGetter.apply(new Material(InventoryMenu.BLOCK_ATLAS, ringsTexture)); + return new BasicRootsBlockBakedModel(barkSprite, ringsSprite); + } + + private BakedModel modifyModelAfterBake(BakedModel model, ModelModifier.AfterBake.Context context) { + ModelResourceLocation modelId = context.topLevelId(); + if (modelId == null) return model; + + if (modelId.id().equals(POTTED_SAPLING_MODEL)) { + return new BakedModelBlockPottedSapling(model); + } + + ResourceLocation blockId = modelId.id(); + Block block = BuiltInRegistries.BLOCK.get(blockId); + + if (block instanceof BasicRootsBlock) { + initBranchModels(material -> context.textureGetter().apply(new Material(material.atlasLocation(), material.texture()))); + + String variant = modelId.variant(); + ResourceLocation cacheKey; + if (variant.contains("layer=filled")) { + cacheKey = blockId.withSuffix("_filled"); + } else if (variant.contains("layer=exposed")) { + cacheKey = blockId.withSuffix("_exposed"); + } else { + return model; + } + + BakedModel rootsModel = UNDERGROUND_ROOTS_MODEL_CACHE.get(cacheKey); + if (rootsModel != null) { + return rootsModel; + } + } + + if (block instanceof BranchBlock) { + initBranchModels(material -> context.textureGetter().apply(new Material(material.atlasLocation(), material.texture()))); + + BakedModel branchModel = BRANCH_MODEL_CACHE.get(blockId); + if (branchModel != null) { + return branchModel; + } + } + + if (block instanceof SurfaceRootBlock) { + initBranchModels(material -> context.textureGetter().apply(new Material(material.atlasLocation(), material.texture()))); + + BakedModel rootModel = ROOT_MODEL_CACHE.get(blockId); + if (rootModel != null) { + return rootModel; + } + } + + return model; + } +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/FallingTreeEntityModelFabric.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/FallingTreeEntityModelFabric.java new file mode 100644 index 000000000..ee05b303c --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/FallingTreeEntityModelFabric.java @@ -0,0 +1,234 @@ +package com.dtteam.dynamictrees.model; + +import com.dtteam.dynamictrees.api.network.BranchDestructionData; +import com.dtteam.dynamictrees.block.branch.BranchBlock; +import com.dtteam.dynamictrees.block.soil.SoilBlock; +import com.dtteam.dynamictrees.entity.FallingTreeEntity; +import com.dtteam.dynamictrees.model.baked.BasicBranchBlockBakedModel; +import com.dtteam.dynamictrees.tree.TreeHelper; +import com.dtteam.dynamictrees.tree.species.Species; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.block.BlockRenderDispatcher; +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.Vec3; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.ArrayList; +import java.util.List; + +public class FallingTreeEntityModelFabric extends FallingTreeEntityModel { + + public FallingTreeEntityModelFabric(FallingTreeEntity entity) { + super(entity); + } + + @Override + public List generateTreeQuads(FallingTreeEntity entity) { + BlockRenderDispatcher dispatcher = Minecraft.getInstance().getBlockRenderer(); + BranchDestructionData destructionData = entity.getDestroyData(); + Direction cutDir = destructionData.cutDir; + + ArrayList treeQuads = new ArrayList<>(); + + int[] connectionArray = new int[6]; + + if (destructionData.getNumBranches() > 0) { + BlockState exState = destructionData.getBranchBlockState(0); + BlockPos cutPos = destructionData.cutPos; + if (exState != null) { + Species species = destructionData.species; + RandomSource random = entity.getRandom(); + + boolean rootyBlockAdded = false; + if (destructionData.soilState != null) { + SoilBlock soilBlock = TreeHelper.getRooty(BuiltInRegistries.BLOCK.get(destructionData.soilState.getLeft())); + if (soilBlock != null) { + BlockState soilState = soilBlock.GetStateFromIndex(destructionData.soilState.getRight()); + BakedModel rootyModel = dispatcher.getBlockModel(soilState); + BlockPos cutOffset = destructionData.getRelativeCutPos(); + treeQuads.addAll(toTreeQuadData( + getQuadsWithOffset(rootyModel, soilState, new Vec3(cutOffset.getX(), cutOffset.getY() - 1, cutOffset.getZ()), random), + destructionData.species.getFamily().getRootColor(soilState, soilBlock.getColorFromBark()), + soilState)); + rootyBlockAdded = true; + } + } + + BakedModel branchModel = dispatcher.getBlockModel(exState); + destructionData.getConnections(0, connectionArray); + boolean bottomRingsAdded = false; + if (!rootyBlockAdded && connectionArray[cutDir.get3DDataValue()] > 0) { + BlockPos offsetPos = destructionData.getRelativeCutPos().relative(cutDir); + float offset = (8 - Math.min(((BranchBlock) exState.getBlock()).getRadius(exState), BranchBlock.MAX_RADIUS)) / 16f; + int coreRadius = ((BranchBlock) exState.getBlock()).getRadius(exState); + treeQuads.addAll(toTreeQuadData( + getBranchQuadsWithConnections(branchModel, exState, new Vec3(offsetPos.getX(), offsetPos.getY(), offsetPos.getZ()).scale(offset), random, connectionArray, coreRadius, cutDir), + exState)); + bottomRingsAdded = true; + } + + for (int index = 0; index < destructionData.getNumBranches(); index++) { + Block previousBranch = exState.getBlock(); + exState = destructionData.getBranchBlockState(index); + if (!previousBranch.equals(exState.getBlock())) { + branchModel = dispatcher.getBlockModel(exState); + } + BlockPos relPos = destructionData.getBranchRelPos(index); + destructionData.getConnections(index, connectionArray); + int coreRadius = ((BranchBlock) exState.getBlock()).getRadius(exState); + Direction forceRingDir = (index == 0 && bottomRingsAdded) ? cutDir : null; + treeQuads.addAll(toTreeQuadData( + getBranchQuadsWithConnections(branchModel, exState, new Vec3(relPos.getX(), relPos.getY(), relPos.getZ()), random, connectionArray, coreRadius, forceRingDir), + exState)); + } + + for (Pair leafLoc : destructionData.getAllLeavesWithPos()) { + BlockState leafState = leafLoc.getValue(); + List bakedQuads = getQuadsWithOffset(dispatcher.getBlockModel(leafState), leafState, + new Vec3(leafLoc.getKey().getX(), leafLoc.getKey().getY(), leafLoc.getKey().getZ()), random); + + treeQuads.addAll(toTreeQuadData(bakedQuads, species.leafColorMultiplier(entity.level(), + cutPos.offset(leafLoc.getKey())), leafState)); + } + } + } + + return treeQuads; + } + + private List getBranchQuadsWithConnections(BakedModel model, BlockState state, Vec3 offset, RandomSource random, int[] connections, int coreRadius, Direction forceRingDir) { + List allQuads = new ArrayList<>(); + + if (model instanceof BasicBranchBlockBakedModel branchModel) { + int twigRadius = 1; + if (state.getBlock() instanceof BranchBlock branchBlock) { + twigRadius = branchBlock.getFamily().getPrimaryThickness(); + } + + int numConnections = 0; + for (int i : connections) { + numConnections += (i != 0) ? 1 : 0; + } + + Direction sourceDir = getSourceDir(coreRadius, connections); + int coreDir = resolveCoreDir(sourceDir); + Direction coreRingDir = forceRingDir != null ? forceRingDir : + ((numConnections == 1 && sourceDir != null) ? sourceDir.getOpposite() : null); + + for (Direction face : Direction.values()) { + if (coreRadius != connections[face.get3DDataValue()]) { + List quads; + if (coreRingDir == null || coreRingDir != face) { + quads = branchModel.coresQuads[coreDir][coreRadius - 1]; + } else { + quads = branchModel.ringsQuads[coreRadius - 1]; + } + for (BakedQuad quad : quads) { + if (quad.getDirection() == face) { + allQuads.add(quad); + } + } + } + + if (coreRadius != 8) { + for (Direction connDir : Direction.values()) { + int idx = connDir.get3DDataValue(); + int connRadius = connections[idx]; + if (connRadius > 0 && (connRadius <= twigRadius || face != connDir)) { + List sleeveQuads = branchModel.sleevesQuads[idx][connRadius - 1]; + if (sleeveQuads != null) { + for (BakedQuad quad : sleeveQuads) { + if (quad.getDirection() == face) { + allQuads.add(quad); + } + } + } + } + } + } + } + } else { + for (Direction direction : Direction.values()) { + allQuads.addAll(model.getQuads(state, direction, random)); + } + allQuads.addAll(model.getQuads(state, null, random)); + } + + if (offset.x() != 0 || offset.y() != 0 || offset.z() != 0) { + List offsetQuads = new ArrayList<>(); + for (BakedQuad quad : allQuads) { + offsetQuads.add(offsetQuad(quad, offset)); + } + return offsetQuads; + } + + return allQuads; + } + + private Direction getSourceDir(int coreRadius, int[] connections) { + int largestConnection = 0; + Direction sourceDir = null; + + for (Direction dir : Direction.values()) { + int connRadius = connections[dir.get3DDataValue()]; + if (connRadius > largestConnection) { + largestConnection = connRadius; + sourceDir = dir; + } + } + + if (largestConnection < coreRadius) { + sourceDir = null; + } + return sourceDir; + } + + private int resolveCoreDir(Direction dir) { + if (dir == null) { + return 0; + } + return dir.get3DDataValue() >> 1; + } + + private List getQuadsWithOffset(BakedModel model, BlockState state, Vec3 offset, RandomSource random) { + List allQuads = new ArrayList<>(); + + for (Direction direction : Direction.values()) { + allQuads.addAll(model.getQuads(state, direction, random)); + } + allQuads.addAll(model.getQuads(state, null, random)); + + if (offset.x() != 0 || offset.y() != 0 || offset.z() != 0) { + List offsetQuads = new ArrayList<>(); + for (BakedQuad quad : allQuads) { + offsetQuads.add(offsetQuad(quad, offset)); + } + return offsetQuads; + } + + return allQuads; + } + + private BakedQuad offsetQuad(BakedQuad quad, Vec3 offset) { + int[] vertexData = quad.getVertices().clone(); + + for (int i = 0; i < 4; i++) { + int baseIndex = i * 8; + float x = Float.intBitsToFloat(vertexData[baseIndex]) + (float) offset.x(); + float y = Float.intBitsToFloat(vertexData[baseIndex + 1]) + (float) offset.y(); + float z = Float.intBitsToFloat(vertexData[baseIndex + 2]) + (float) offset.z(); + vertexData[baseIndex] = Float.floatToRawIntBits(x); + vertexData[baseIndex + 1] = Float.floatToRawIntBits(y); + vertexData[baseIndex + 2] = Float.floatToRawIntBits(z); + } + + return new BakedQuad(vertexData, quad.getTintIndex(), quad.getDirection(), quad.getSprite(), quad.isShade()); + } +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/BasicBranchBlockBakedModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/BasicBranchBlockBakedModel.java new file mode 100644 index 000000000..6e104eb5c --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/BasicBranchBlockBakedModel.java @@ -0,0 +1,303 @@ +package com.dtteam.dynamictrees.model.baked; + +import com.dtteam.dynamictrees.block.branch.BranchBlock; +import com.google.common.collect.Maps; +import net.fabricmc.fabric.api.renderer.v1.mesh.*; +import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel; +import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.renderer.block.model.BlockElement; +import net.minecraft.client.renderer.block.model.BlockElementFace; +import net.minecraft.client.renderer.block.model.BlockFaceUV; +import net.minecraft.client.renderer.block.model.FaceBakery; +import net.minecraft.client.renderer.block.model.ItemOverrides; +import net.minecraft.client.renderer.block.model.ItemTransforms; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.client.resources.model.BlockModelRotation; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Direction.Axis; +import net.minecraft.core.Direction.AxisDirection; +import net.minecraft.util.RandomSource; +import net.minecraft.util.RandomSource; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.BlockAndTintGetter; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.Nullable; +import org.joml.Vector3f; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +@SuppressWarnings("unchecked") +public class BasicBranchBlockBakedModel implements BakedModel, FabricBakedModel { + + protected final TextureAtlasSprite barkTexture; + protected final TextureAtlasSprite ringsTexture; + + public final List[][] sleevesQuads = new List[6][7]; + public final List[][] coresQuads = new List[3][8]; + public final List[] ringsQuads = new List[8]; + + public BasicBranchBlockBakedModel(TextureAtlasSprite barkTexture, TextureAtlasSprite ringsTexture) { + this.barkTexture = barkTexture; + this.ringsTexture = ringsTexture; + initModels(); + } + + private void initModels() { + for (int i = 0; i < 8; i++) { + int radius = i + 1; + if (radius < 8) { + for (Direction dir : Direction.values()) { + sleevesQuads[dir.get3DDataValue()][i] = bakeSleeve(radius, dir, barkTexture); + } + } + coresQuads[0][i] = bakeCore(radius, Axis.Y, barkTexture); + coresQuads[1][i] = bakeCore(radius, Axis.Z, barkTexture); + coresQuads[2][i] = bakeCore(radius, Axis.X, barkTexture); + + ringsQuads[i] = bakeCore(radius, Axis.Y, ringsTexture); + } + } + + public BlockElement generateSleevePart(int radius, Direction dir) { + int dradius = radius * 2; + int halfSize = (16 - dradius) / 2; + int halfSizeX = dir.getStepX() != 0 ? halfSize : dradius; + int halfSizeY = dir.getStepY() != 0 ? halfSize : dradius; + int halfSizeZ = dir.getStepZ() != 0 ? halfSize : dradius; + int move = 16 - halfSize; + int centerX = 16 + (dir.getStepX() * move); + int centerY = 16 + (dir.getStepY() * move); + int centerZ = 16 + (dir.getStepZ() * move); + + Vector3f posFrom = new Vector3f((centerX - halfSizeX) / 2f, (centerY - halfSizeY) / 2f, (centerZ - halfSizeZ) / 2f); + Vector3f posTo = new Vector3f((centerX + halfSizeX) / 2f, (centerY + halfSizeY) / 2f, (centerZ + halfSizeZ) / 2f); + + boolean negative = dir.getAxisDirection() == AxisDirection.NEGATIVE; + if (dir.getAxis() == Axis.Z) { + negative = !negative; + } + + Map mapFacesIn = Maps.newEnumMap(Direction.class); + + for (Direction face : Direction.values()) { + if (dir.getOpposite() != face) { + BlockFaceUV uvface = null; + if (dir == face) { + if (radius == 1) { + uvface = new BlockFaceUV(new float[]{8 - radius, 8 - radius, 8 + radius, 8 + radius}, 0); + } + } else { + uvface = new BlockFaceUV(new float[]{8 - radius, negative ? 16 - halfSize : 0, 8 + radius, negative ? 16 : halfSize}, getFaceAngle(dir.getAxis(), face)); + } + if (uvface != null) { + mapFacesIn.put(face, new BlockElementFace(null, -1, null, uvface)); + } + } + } + + return new BlockElement(posFrom, posTo, mapFacesIn, null, true); + } + + public List bakeSleeve(int radius, Direction dir, TextureAtlasSprite bark) { + BlockElement part = generateSleevePart(radius, dir); + List quads = new ArrayList<>(); + FaceBakery faceBakery = new FaceBakery(); + + for (Map.Entry e : part.faces.entrySet()) { + Direction face = e.getKey(); + quads.add(faceBakery.bakeQuad(part.from, part.to, e.getValue(), bark, face, BlockModelRotation.X0_Y0, part.rotation, true)); + } + + return quads; + } + + protected BlockElement generateCorePart(int radius, Axis axis) { + Vector3f posFrom = new Vector3f(8 - radius, 8 - radius, 8 - radius); + Vector3f posTo = new Vector3f(8 + radius, 8 + radius, 8 + radius); + + Map mapFacesIn = Maps.newEnumMap(Direction.class); + + for (Direction face : Direction.values()) { + BlockFaceUV uvface = new BlockFaceUV(new float[]{8 - radius, 8 - radius, 8 + radius, 8 + radius}, getFaceAngle(axis, face)); + mapFacesIn.put(face, new BlockElementFace(null, -1, null, uvface)); + } + + return new BlockElement(posFrom, posTo, mapFacesIn, null, true); + } + + public List bakeCore(int radius, Axis axis, TextureAtlasSprite icon) { + BlockElement part = generateCorePart(radius, axis); + List quads = new ArrayList<>(); + FaceBakery faceBakery = new FaceBakery(); + + for (Map.Entry e : part.faces.entrySet()) { + Direction face = e.getKey(); + quads.add(faceBakery.bakeQuad(part.from, part.to, e.getValue(), icon, face, BlockModelRotation.X0_Y0, part.rotation, true)); + } + + return quads; + } + + public int getFaceAngle(Axis axis, Direction face) { + if (axis == Axis.Y) { + return 0; + } else if (axis == Axis.Z) { + return switch (face) { + case UP -> 0; + case WEST -> 270; + case DOWN -> 180; + default -> 90; + }; + } else { + return (face == Direction.NORTH) ? 270 : 90; + } + } + + @Override + public boolean isVanillaAdapter() { + return false; + } + + @Override + public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, BlockPos pos, Supplier randomSupplier, RenderContext context) { + if (state == null) return; + + final int coreRadius = getRadius(state); + if (coreRadius <= 0 || coreRadius > 8) return; + + int[] connections = new int[]{0, 0, 0, 0, 0, 0}; + int twigRadius = 1; + + if (state.getBlock() instanceof BranchBlock branchBlock) { + connections = branchBlock.getConnectionData(blockView, pos, state).getAllRadii(); + twigRadius = branchBlock.getFamily().getPrimaryThickness(); + } + + int numConnections = 0; + for (int i : connections) { + numConnections += (i != 0) ? 1 : 0; + } + + QuadEmitter emitter = context.getEmitter(); + + Direction sourceDir = getSourceDir(coreRadius, connections); + int coreDir = resolveCoreDir(sourceDir); + Direction coreRingDir = (numConnections == 1 && sourceDir != null) ? sourceDir.getOpposite() : null; + + for (Direction face : Direction.values()) { + if (coreRadius != connections[face.get3DDataValue()]) { + List quads; + if (coreRingDir == null || coreRingDir != face) { + quads = coresQuads[coreDir][coreRadius - 1]; + } else { + quads = ringsQuads[coreRadius - 1]; + } + for (BakedQuad quad : quads) { + if (quad.getDirection() == face) { + emitter.fromVanilla(quad, null, face); + emitter.emit(); + } + } + } + + if (coreRadius != 8) { + for (Direction connDir : Direction.values()) { + int idx = connDir.get3DDataValue(); + int connRadius = connections[idx]; + if (connRadius > 0 && (connRadius <= twigRadius || face != connDir)) { + List sleeveQuads = sleevesQuads[idx][connRadius - 1]; + if (sleeveQuads != null) { + for (BakedQuad quad : sleeveQuads) { + if (quad.getDirection() == face) { + emitter.fromVanilla(quad, null, face); + emitter.emit(); + } + } + } + } + } + } + } + } + + @Override + public void emitItemQuads(ItemStack stack, Supplier randomSupplier, RenderContext context) { + } + + @Nullable + protected Direction getSourceDir(int coreRadius, int[] connections) { + int largestConnection = 0; + Direction sourceDir = null; + + for (Direction dir : Direction.values()) { + int connRadius = connections[dir.get3DDataValue()]; + if (connRadius > largestConnection) { + largestConnection = connRadius; + sourceDir = dir; + } + } + + if (largestConnection < coreRadius) { + sourceDir = null; + } + return sourceDir; + } + + protected int resolveCoreDir(@Nullable Direction dir) { + if (dir == null) { + return 0; + } + return dir.get3DDataValue() >> 1; + } + + protected int getRadius(BlockState blockState) { + return ((BranchBlock) blockState.getBlock()).getRadius(blockState); + } + + @Override + public List getQuads(@Nullable BlockState state, @Nullable Direction direction, RandomSource random) { + return Collections.emptyList(); + } + + @Override + public boolean useAmbientOcclusion() { + return true; + } + + @Override + public boolean isGui3d() { + return false; + } + + @Override + public boolean usesBlockLight() { + return false; + } + + @Override + public boolean isCustomRenderer() { + return false; + } + + @Override + public TextureAtlasSprite getParticleIcon() { + return barkTexture; + } + + @Override + public ItemTransforms getTransforms() { + return ItemTransforms.NO_TRANSFORMS; + } + + @Override + public ItemOverrides getOverrides() { + return ItemOverrides.EMPTY; + } +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/BasicRootsBlockBakedModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/BasicRootsBlockBakedModel.java new file mode 100644 index 000000000..0a8a7d7b7 --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/BasicRootsBlockBakedModel.java @@ -0,0 +1,160 @@ +package com.dtteam.dynamictrees.model.baked; + +import com.dtteam.dynamictrees.block.branch.BasicRootsBlock; +import com.dtteam.dynamictrees.block.branch.BranchBlock; +import com.google.common.collect.Maps; +import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.renderer.block.model.BlockElement; +import net.minecraft.client.renderer.block.model.BlockElementFace; +import net.minecraft.client.renderer.block.model.BlockFaceUV; +import net.minecraft.client.renderer.block.model.FaceBakery; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.resources.model.BlockModelRotation; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.BlockAndTintGetter; +import net.minecraft.world.level.block.state.BlockState; +import org.joml.Vector3f; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +@SuppressWarnings("unchecked") +public class BasicRootsBlockBakedModel extends BasicBranchBlockBakedModel { + + final static float Z_FIGHTING_OFFSET = 0.001f; + + private final List[][] sleeveEndFaces = new List[6][8]; + + public BasicRootsBlockBakedModel(TextureAtlasSprite barkTexture, TextureAtlasSprite ringsTexture) { + super(barkTexture, ringsTexture); + initRootsModels(); + } + + private void initRootsModels() { + for (int i = 0; i < 8; i++) { + int radius = i + 1; + for (Direction dir : Direction.values()) { + sleeveEndFaces[dir.get3DDataValue()][i] = bakeSleeveFace(radius, dir, ringsTexture); + } + } + } + + public List bakeSleeveFace(int radius, Direction dir, TextureAtlasSprite rings) { + int dradius = radius * 2; + int halfSize = (16 - dradius) / 2; + float halfSizeX = dir.getStepX() != 0 ? halfSize + Z_FIGHTING_OFFSET : dradius; + float halfSizeY = dir.getStepY() != 0 ? halfSize + Z_FIGHTING_OFFSET : dradius; + float halfSizeZ = dir.getStepZ() != 0 ? halfSize + Z_FIGHTING_OFFSET : dradius; + int move = 16 - halfSize; + int centerX = 16 + (dir.getStepX() * move); + int centerY = 16 + (dir.getStepY() * move); + int centerZ = 16 + (dir.getStepZ() * move); + + Vector3f posFrom = new Vector3f((centerX - halfSizeX) / 2f, (centerY - halfSizeY) / 2f, (centerZ - halfSizeZ) / 2f); + Vector3f posTo = new Vector3f((centerX + halfSizeX) / 2f, (centerY + halfSizeY) / 2f, (centerZ + halfSizeZ) / 2f); + + Map mapFacesIn = Maps.newEnumMap(Direction.class); + BlockFaceUV uvface = new BlockFaceUV(new float[]{8 - radius, 8 - radius, 8 + radius, 8 + radius}, 0); + mapFacesIn.put(dir, new BlockElementFace(dir, -1, "", uvface)); + + BlockElement part = new BlockElement(posFrom, posTo, mapFacesIn, null, true); + List quads = new ArrayList<>(); + FaceBakery faceBakery = new FaceBakery(); + + for (Map.Entry e : part.faces.entrySet()) { + Direction face = e.getKey(); + quads.add(faceBakery.bakeQuad(part.from, part.to, e.getValue(), rings, face, BlockModelRotation.X0_Y0, part.rotation, true)); + } + + return quads; + } + + @Override + protected int getRadius(BlockState blockState) { + if (blockState.getBlock() instanceof BasicRootsBlock rootsBlock) { + return rootsBlock.getRadius(blockState); + } + return super.getRadius(blockState); + } + + @Override + public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, BlockPos pos, Supplier randomSupplier, RenderContext context) { + if (state == null) return; + + final int coreRadius = getRadius(state); + if (coreRadius <= 0 || coreRadius > 8) return; + + int[] connections = new int[]{0, 0, 0, 0, 0, 0}; + int twigRadius = 1; + + if (state.getBlock() instanceof BranchBlock branchBlock) { + connections = branchBlock.getConnectionData(blockView, pos, state).getAllRadii(); + twigRadius = branchBlock.getFamily().getPrimaryThickness(); + } + + int numConnections = 0; + for (int i : connections) { + numConnections += (i != 0) ? 1 : 0; + } + + var emitter = context.getEmitter(); + + Direction sourceDir = getSourceDir(coreRadius, connections); + int coreDir = resolveCoreDir(sourceDir); + Direction coreRingDir = (numConnections == 1 && sourceDir != null) ? sourceDir.getOpposite() : null; + + for (Direction face : Direction.values()) { + if (coreRadius != connections[face.get3DDataValue()]) { + List quads; + if (coreRingDir == null || coreRingDir != face) { + quads = coresQuads[coreDir][coreRadius - 1]; + } else { + quads = ringsQuads[coreRadius - 1]; + } + for (BakedQuad quad : quads) { + if (quad.getDirection() == face) { + emitter.fromVanilla(quad, null, face); + emitter.emit(); + } + } + } + + if (coreRadius != 8) { + for (Direction connDir : Direction.values()) { + int idx = connDir.get3DDataValue(); + int connRadius = connections[idx]; + if (connRadius > 0 && (connRadius <= twigRadius || face != connDir)) { + List sleeveQuads = sleevesQuads[idx][connRadius - 1]; + if (sleeveQuads != null) { + for (BakedQuad quad : sleeveQuads) { + if (quad.getDirection() == face) { + emitter.fromVanilla(quad, null, face); + emitter.emit(); + } + } + } + } + } + } + + int idx = face.get3DDataValue(); + int connRadius = connections[idx]; + if (connRadius > 0) { + List endFaceQuads = sleeveEndFaces[idx][connRadius - 1]; + if (endFaceQuads != null) { + for (BakedQuad quad : endFaceQuads) { + if (quad.getDirection() == face) { + emitter.fromVanilla(quad, null, face); + emitter.emit(); + } + } + } + } + } + } +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/SurfaceRootBlockBakedModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/SurfaceRootBlockBakedModel.java new file mode 100644 index 000000000..b15ba0b85 --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/SurfaceRootBlockBakedModel.java @@ -0,0 +1,361 @@ +package com.dtteam.dynamictrees.model.baked; + +import com.dtteam.dynamictrees.api.network.RootConnections; +import com.dtteam.dynamictrees.block.branch.SurfaceRootBlock; +import com.dtteam.dynamictrees.utility.CoordUtils; +import com.google.common.collect.Maps; +import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter; +import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel; +import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.renderer.block.model.BlockElement; +import net.minecraft.client.renderer.block.model.BlockElementFace; +import net.minecraft.client.renderer.block.model.BlockFaceUV; +import net.minecraft.client.renderer.block.model.FaceBakery; +import net.minecraft.client.renderer.block.model.ItemOverrides; +import net.minecraft.client.renderer.block.model.ItemTransforms; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.client.resources.model.BlockModelRotation; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.util.Mth; +import net.minecraft.util.RandomSource; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.BlockAndTintGetter; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.AABB; +import org.jetbrains.annotations.Nullable; +import org.joml.Vector3f; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +@SuppressWarnings("unchecked") +public class SurfaceRootBlockBakedModel implements BakedModel, FabricBakedModel { + + protected final TextureAtlasSprite barkTexture; + + public final List[][] sleevesQuads = new List[4][7]; + public final List[][] coresQuads = new List[2][8]; + public final List[][] vertsQuads = new List[4][8]; + + public SurfaceRootBlockBakedModel(TextureAtlasSprite barkTexture) { + this.barkTexture = barkTexture; + initModels(); + } + + private void initModels() { + for (int r = 0; r < 8; r++) { + int radius = r + 1; + if (radius < 8) { + for (Direction dir : CoordUtils.HORIZONTALS) { + int horIndex = dir.get2DDataValue(); + sleevesQuads[horIndex][r] = bakeSleeve(radius, dir); + vertsQuads[horIndex][r] = bakeVert(radius, dir); + } + } + coresQuads[0][r] = bakeCore(radius, Direction.Axis.Z); + coresQuads[1][r] = bakeCore(radius, Direction.Axis.X); + } + } + + public int getRadialHeight(int radius) { + return radius * 2; + } + + public List bakeSleeve(int radius, Direction dir) { + int radialHeight = getRadialHeight(radius); + + int dradius = radius * 2; + int halfSize = (16 - dradius) / 2; + int halfSizeX = dir.getStepX() != 0 ? halfSize : dradius; + int halfSizeZ = dir.getStepZ() != 0 ? halfSize : dradius; + int move = 16 - halfSize; + int centerX = 16 + (dir.getStepX() * move); + int centerZ = 16 + (dir.getStepZ() * move); + + Vector3f posFrom = new Vector3f((centerX - halfSizeX) / 2f, 0, (centerZ - halfSizeZ) / 2f); + Vector3f posTo = new Vector3f((centerX + halfSizeX) / 2f, radialHeight, (centerZ + halfSizeZ) / 2f); + + boolean sleeveNegative = dir.getAxisDirection() == Direction.AxisDirection.NEGATIVE; + if (dir.getAxis() == Direction.Axis.Z) { + sleeveNegative = !sleeveNegative; + } + + Map mapFacesIn = Maps.newEnumMap(Direction.class); + + for (Direction face : Direction.values()) { + if (dir.getOpposite() != face) { + BlockFaceUV uvface; + if (face.getAxis().isHorizontal()) { + boolean facePositive = face.getAxisDirection() == Direction.AxisDirection.POSITIVE; + uvface = new BlockFaceUV(new float[]{facePositive ? 16 - radialHeight : 0, (sleeveNegative ? 16 - halfSize : 0), facePositive ? 16 : radialHeight, (sleeveNegative ? 16 : halfSize)}, getFaceAngle(dir.getAxis(), face)); + } else { + uvface = new BlockFaceUV(new float[]{8 - radius, sleeveNegative ? 16 - halfSize : 0, 8 + radius, sleeveNegative ? 16 : halfSize}, getFaceAngle(dir.getAxis(), face)); + } + mapFacesIn.put(face, new BlockElementFace(null, -1, null, uvface)); + } + } + + BlockElement part = new BlockElement(posFrom, posTo, mapFacesIn, null, true); + List quads = new ArrayList<>(); + FaceBakery faceBakery = new FaceBakery(); + + for (Map.Entry e : part.faces.entrySet()) { + Direction face = e.getKey(); + quads.add(faceBakery.bakeQuad(part.from, part.to, e.getValue(), barkTexture, face, BlockModelRotation.X0_Y0, part.rotation, true)); + } + + return quads; + } + + private List bakeVert(int radius, Direction dir) { + int radialHeight = getRadialHeight(radius); + List quads = new ArrayList<>(); + FaceBakery faceBakery = new FaceBakery(); + + AABB partBoundary = new AABB(8 - radius, radialHeight, 8 - radius, 8 + radius, 16 + radialHeight, 8 + radius) + .move(dir.getStepX() * 7, 0, dir.getStepZ() * 7); + + for (int i = 0; i < 2; i++) { + AABB pieceBoundary = partBoundary.intersect(new AABB(0, 0, 0, 16, 16, 16).move(0, 16 * i, 0)); + + for (Direction face : Direction.values()) { + Map mapFacesIn = Maps.newEnumMap(Direction.class); + + BlockFaceUV uvface = new BlockFaceUV(modUV(getUVs(pieceBoundary, face)), getFaceAngle(Direction.Axis.Y, face)); + mapFacesIn.put(face, new BlockElementFace(null, -1, null, uvface)); + + Vector3f[] limits = AABBLimits(pieceBoundary); + + BlockElement part = new BlockElement(limits[0], limits[1], mapFacesIn, null, true); + quads.add(faceBakery.bakeQuad(part.from, part.to, part.faces.get(face), barkTexture, face, BlockModelRotation.X0_Y0, part.rotation, true)); + } + } + + return quads; + } + + public List bakeCore(int radius, Direction.Axis axis) { + int radialHeight = getRadialHeight(radius); + + Vector3f posFrom = new Vector3f(8 - radius, 0, 8 - radius); + Vector3f posTo = new Vector3f(8 + radius, radialHeight, 8 + radius); + + Map mapFacesIn = Maps.newEnumMap(Direction.class); + + for (Direction face : Direction.values()) { + BlockFaceUV uvface; + if (face.getAxis().isHorizontal()) { + boolean positive = face.getAxisDirection() == Direction.AxisDirection.POSITIVE; + uvface = new BlockFaceUV(new float[]{positive ? 16 - radialHeight : 0, 8 - radius, positive ? 16 : radialHeight, 8 + radius}, getFaceAngle(axis, face)); + } else { + uvface = new BlockFaceUV(new float[]{8 - radius, 8 - radius, 8 + radius, 8 + radius}, getFaceAngle(axis, face)); + } + + mapFacesIn.put(face, new BlockElementFace(null, -1, null, uvface)); + } + + BlockElement part = new BlockElement(posFrom, posTo, mapFacesIn, null, true); + List quads = new ArrayList<>(); + FaceBakery faceBakery = new FaceBakery(); + + for (Map.Entry e : part.faces.entrySet()) { + Direction face = e.getKey(); + quads.add(faceBakery.bakeQuad(part.from, part.to, e.getValue(), barkTexture, face, BlockModelRotation.X0_Y0, part.rotation, true)); + } + + return quads; + } + + public int getFaceAngle(Direction.Axis axis, Direction face) { + if (axis == Direction.Axis.Y) { + return 0; + } else if (axis == Direction.Axis.Z) { + return switch (face) { + case UP -> 0; + case WEST, NORTH -> 270; + case DOWN -> 180; + default -> 90; + }; + } else { + return (face == Direction.NORTH) ? 270 : 90; + } + } + + public float[] getUVs(AABB box, Direction face) { + return switch (face) { + case UP -> new float[]{(float) box.minX, (float) box.minZ, (float) box.maxX, (float) box.maxZ}; + case NORTH -> new float[]{16f - (float) box.maxX, (float) box.minY, 16f - (float) box.minX, (float) box.maxY}; + case SOUTH -> new float[]{(float) box.minX, (float) box.minY, (float) box.maxX, (float) box.maxY}; + case WEST -> new float[]{(float) box.minZ, (float) box.minY, (float) box.maxZ, (float) box.maxY}; + case EAST -> new float[]{16f - (float) box.maxZ, (float) box.minY, 16f - (float) box.minZ, (float) box.maxY}; + default -> new float[]{(float) box.minX, 16f - (float) box.minZ, (float) box.maxX, 16f - (float) box.maxZ}; + }; + } + + public float[] modUV(float[] uvs) { + uvs[0] = (int) uvs[0] & 0xf; + uvs[1] = (int) uvs[1] & 0xf; + uvs[2] = (((int) uvs[2] - 1) & 0xf) + 1; + uvs[3] = (((int) uvs[3] - 1) & 0xf) + 1; + return uvs; + } + + public Vector3f[] AABBLimits(AABB aabb) { + return new Vector3f[]{ + new Vector3f((float) aabb.minX, (float) aabb.minY, (float) aabb.minZ), + new Vector3f((float) aabb.maxX, (float) aabb.maxY, (float) aabb.maxZ), + }; + } + + @Override + public boolean isVanillaAdapter() { + return false; + } + + @Override + public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, BlockPos pos, Supplier randomSupplier, RenderContext context) { + if (state == null) return; + + int coreRadius = getRadius(state); + if (coreRadius <= 0 || coreRadius > 8) return; + + int[] connections = new int[]{0, 0, 0, 0}; + RootConnections.ConnectionLevel[] connectionLevels = RootConnections.PLACEHOLDER_CONNECTION_LEVELS.clone(); + + if (state.getBlock() instanceof SurfaceRootBlock surfaceRootBlock) { + RootConnections connectionData = surfaceRootBlock.getConnectionData(blockView, pos); + connections = connectionData.getAllRadii(); + connectionLevels = connectionData.getConnectionLevels(); + } + + for (int i = 0; i < connections.length; i++) { + connections[i] = Mth.clamp(connections[i], 0, coreRadius); + } + + Direction sourceDir = getSourceDir(coreRadius, connections); + if (sourceDir == null) { + sourceDir = Direction.DOWN; + } + int coreDir = resolveCoreDir(sourceDir); + + boolean isGrounded = state.getValue(SurfaceRootBlock.GROUNDED); + + QuadEmitter emitter = context.getEmitter(); + + for (Direction face : Direction.values()) { + if (isGrounded) { + List coreQuads = coresQuads[coreDir][coreRadius - 1]; + if (coreQuads != null) { + for (BakedQuad quad : coreQuads) { + if (quad.getDirection() == face) { + emitter.fromVanilla(quad, null, face); + emitter.emit(); + } + } + } + } + + if (coreRadius != 8) { + for (Direction connDir : CoordUtils.HORIZONTALS) { + int idx = connDir.get2DDataValue(); + int connRadius = connections[idx]; + if (connRadius > 0) { + if (isGrounded && sleevesQuads[idx][connRadius - 1] != null) { + for (BakedQuad quad : sleevesQuads[idx][connRadius - 1]) { + if (quad.getDirection() == face) { + emitter.fromVanilla(quad, null, face); + emitter.emit(); + } + } + } + if (connectionLevels[idx] == RootConnections.ConnectionLevel.HIGH && vertsQuads[idx][connRadius - 1] != null) { + for (BakedQuad quad : vertsQuads[idx][connRadius - 1]) { + if (quad.getDirection() == face) { + emitter.fromVanilla(quad, null, face); + emitter.emit(); + } + } + } + } + } + } + } + } + + @Override + public void emitItemQuads(ItemStack stack, Supplier randomSupplier, RenderContext context) { + } + + protected Direction getSourceDir(int coreRadius, int[] connections) { + int largestConnection = 0; + Direction sourceDir = null; + + for (Direction dir : CoordUtils.HORIZONTALS) { + int horIndex = dir.get2DDataValue(); + int connRadius = connections[horIndex]; + if (connRadius > largestConnection) { + largestConnection = connRadius; + sourceDir = dir; + } + } + + if (largestConnection < coreRadius) { + sourceDir = null; + } + return sourceDir; + } + + protected int resolveCoreDir(Direction dir) { + return dir.getAxis() == Direction.Axis.X ? 1 : 0; + } + + protected int getRadius(BlockState blockState) { + return ((SurfaceRootBlock) blockState.getBlock()).getRadius(blockState); + } + + @Override + public List getQuads(@Nullable BlockState state, @Nullable Direction direction, RandomSource random) { + return Collections.emptyList(); + } + + @Override + public boolean useAmbientOcclusion() { + return true; + } + + @Override + public boolean isGui3d() { + return false; + } + + @Override + public boolean usesBlockLight() { + return false; + } + + @Override + public boolean isCustomRenderer() { + return true; + } + + @Override + public TextureAtlasSprite getParticleIcon() { + return barkTexture; + } + + @Override + public ItemTransforms getTransforms() { + return ItemTransforms.NO_TRANSFORMS; + } + + @Override + public ItemOverrides getOverrides() { + return ItemOverrides.EMPTY; + } +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/ThickBranchBlockBakedModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/ThickBranchBlockBakedModel.java new file mode 100644 index 000000000..d7e10ccc2 --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/ThickBranchBlockBakedModel.java @@ -0,0 +1,235 @@ +package com.dtteam.dynamictrees.model.baked; + +import com.dtteam.dynamictrees.block.branch.BranchBlock; +import com.dtteam.dynamictrees.block.branch.ThickBranchBlock; +import com.dtteam.dynamictrees.utility.CoordUtils; +import com.dtteam.dynamictrees.utility.CoordUtils.Surround; +import com.google.common.collect.Maps; +import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.renderer.block.model.BlockElement; +import net.minecraft.client.renderer.block.model.BlockElementFace; +import net.minecraft.client.renderer.block.model.BlockFaceUV; +import net.minecraft.client.renderer.block.model.FaceBakery; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.resources.model.BlockModelRotation; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Direction.Axis; +import net.minecraft.core.Vec3i; +import net.minecraft.util.Mth; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.BlockAndTintGetter; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import org.joml.Vector3f; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +@SuppressWarnings("unchecked") +public class ThickBranchBlockBakedModel extends BasicBranchBlockBakedModel { + + private final TextureAtlasSprite thickRingsTexture; + private final List[] trunksBarkQuads = new List[16]; + private final List[] trunksTopBarkQuads = new List[16]; + private final List[] trunksTopRingsQuads = new List[16]; + private final List[] trunksBotRingsQuads = new List[16]; + + public ThickBranchBlockBakedModel(TextureAtlasSprite barkTexture, TextureAtlasSprite ringsTexture, TextureAtlasSprite thickRingsTexture) { + super(barkTexture, ringsTexture); + this.thickRingsTexture = thickRingsTexture; + initThickModels(); + } + + private void initThickModels() { + for (int i = 0; i < ThickBranchBlock.MAX_RADIUS_THICK - ThickBranchBlock.MAX_RADIUS; i++) { + int radius = i + ThickBranchBlock.MAX_RADIUS + 1; + trunksBarkQuads[i] = bakeTrunkBark(radius, this.barkTexture, true); + trunksTopBarkQuads[i] = bakeTrunkBark(radius, this.barkTexture, false); + trunksTopRingsQuads[i] = bakeTrunkRings(radius, thickRingsTexture, Direction.UP); + trunksBotRingsQuads[i] = bakeTrunkRings(radius, thickRingsTexture, Direction.DOWN); + } + } + + public List bakeTrunkBark(int radius, TextureAtlasSprite bark, boolean side) { + List quads = new ArrayList<>(); + FaceBakery faceBakery = new FaceBakery(); + AABB wholeVolume = new AABB(8 - radius, 0, 8 - radius, 8 + radius, 16, 8 + radius); + + final Direction[] run = side ? CoordUtils.HORIZONTALS : new Direction[]{Direction.UP, Direction.DOWN}; + ArrayList offsets = new ArrayList<>(); + + for (Surround dir : Surround.values()) { + offsets.add(dir.getOffset()); + } + offsets.add(new Vec3i(0, 0, 0)); + + for (Direction face : run) { + final Vec3i dirVector = face.getNormal(); + + for (Vec3i offset : offsets) { + if (face.getAxis() == Axis.Y || new Vec3(dirVector.getX(), dirVector.getY(), dirVector.getZ()).add(new Vec3(offset.getX(), offset.getY(), offset.getZ())).lengthSqr() > 2.25) { + Vec3 scaledOffset = new Vec3(offset.getX() * 16, offset.getY() * 16, offset.getZ() * 16); + AABB partBoundary = new AABB(0, 0, 0, 16, 16, 16).move(scaledOffset).intersect(wholeVolume); + + Vector3f[] limits = aabbLimits(partBoundary); + + Map mapFacesIn = Maps.newEnumMap(Direction.class); + + BlockFaceUV uvface = new BlockFaceUV(modUV(getUVs(partBoundary, face)), getFaceAngle(Axis.Y, face)); + mapFacesIn.put(face, new BlockElementFace(null, -1, null, uvface)); + + BlockElement part = new BlockElement(limits[0], limits[1], mapFacesIn, null, true); + quads.add(faceBakery.bakeQuad(part.from, part.to, part.faces.get(face), bark, face, BlockModelRotation.X0_Y0, part.rotation, true)); + } + } + } + + return quads; + } + + public List bakeTrunkRings(int radius, TextureAtlasSprite ring, Direction face) { + List quads = new ArrayList<>(); + FaceBakery faceBakery = new FaceBakery(); + AABB wholeVolume = new AABB(8 - radius, 0, 8 - radius, 8 + radius, 16, 8 + radius); + int wholeVolumeWidth = 48; + + ArrayList offsets = new ArrayList<>(); + + for (Surround dir : Surround.values()) { + offsets.add(dir.getOffset()); + } + offsets.add(new Vec3i(0, 0, 0)); + + for (Vec3i offset : offsets) { + Vec3 scaledOffset = new Vec3(offset.getX() * 16, offset.getY() * 16, offset.getZ() * 16); + AABB partBoundary = new AABB(0, 0, 0, 16, 16, 16).move(scaledOffset).intersect(wholeVolume); + + Vector3f posFrom = new Vector3f((float) partBoundary.minX, (float) partBoundary.minY, (float) partBoundary.minZ); + Vector3f posTo = new Vector3f((float) partBoundary.maxX, (float) partBoundary.maxY, (float) partBoundary.maxZ); + + Map mapFacesIn = Maps.newEnumMap(Direction.class); + float[] uvs = getRingsUvs(face, partBoundary, wholeVolumeWidth); + + BlockFaceUV uvFace = new BlockFaceUV(uvs, getFaceAngle(Axis.Y, face)); + mapFacesIn.put(face, new BlockElementFace(null, -1, null, uvFace)); + + BlockElement part = new BlockElement(posFrom, posTo, mapFacesIn, null, true); + quads.add(faceBakery.bakeQuad(part.from, part.to, part.faces.get(face), ring, face, BlockModelRotation.X0_Y0, part.rotation, true)); + } + + return quads; + } + + private static float[] getRingsUvs(Direction face, AABB partBoundary, int wholeVolumeWidth) { + float textureOffsetX = -16f; + float textureOffsetZ = -16f; + + float minX = ((float) ((partBoundary.minX - textureOffsetX) / wholeVolumeWidth)) * 16f; + float maxX = ((float) ((partBoundary.maxX - textureOffsetX) / wholeVolumeWidth)) * 16f; + float minZ = ((float) ((partBoundary.minZ - textureOffsetZ) / wholeVolumeWidth)) * 16f; + float maxZ = ((float) ((partBoundary.maxZ - textureOffsetZ) / wholeVolumeWidth)) * 16f; + + if (face == Direction.DOWN) { + minZ = ((float) ((partBoundary.maxZ - textureOffsetZ) / wholeVolumeWidth)) * 16f; + maxZ = ((float) ((partBoundary.minZ - textureOffsetZ) / wholeVolumeWidth)) * 16f; + } + + return new float[]{minX, minZ, maxX, maxZ}; + } + + public static float[] getUVs(AABB box, Direction face) { + return switch (face) { + case UP -> new float[]{(float) box.minX, (float) box.minZ, (float) box.maxX, (float) box.maxZ}; + case NORTH -> new float[]{16f - (float) box.maxX, (float) box.minY, 16f - (float) box.minX, (float) box.maxY}; + case SOUTH -> new float[]{(float) box.minX, (float) box.minY, (float) box.maxX, (float) box.maxY}; + case WEST -> new float[]{(float) box.minZ, (float) box.minY, (float) box.maxZ, (float) box.maxY}; + case EAST -> new float[]{16f - (float) box.maxZ, (float) box.minY, 16f - (float) box.minZ, (float) box.maxY}; + default -> new float[]{(float) box.minX, 16f - (float) box.minZ, (float) box.maxX, 16f - (float) box.maxZ}; + }; + } + + public static float[] modUV(float[] uvs) { + uvs[0] = (int) uvs[0] & 0xf; + uvs[1] = (int) uvs[1] & 0xf; + uvs[2] = (((int) uvs[2] - 1) & 0xf) + 1; + uvs[3] = (((int) uvs[3] - 1) & 0xf) + 1; + return uvs; + } + + public static Vector3f[] aabbLimits(AABB aabb) { + return new Vector3f[]{ + new Vector3f((float) aabb.minX, (float) aabb.minY, (float) aabb.minZ), + new Vector3f((float) aabb.maxX, (float) aabb.maxY, (float) aabb.maxZ), + }; + } + + @Override + public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, BlockPos pos, Supplier randomSupplier, RenderContext context) { + if (state == null) return; + + int coreRadius = getRadius(state); + + if (coreRadius <= BranchBlock.MAX_RADIUS) { + super.emitBlockQuads(blockView, state, pos, randomSupplier, context); + return; + } + + coreRadius = Mth.clamp(coreRadius, 9, 24); + + int[] connections = new int[]{0, 0, 0, 0, 0, 0}; + int twigRadius = 1; + + if (state.getBlock() instanceof BranchBlock branchBlock) { + connections = branchBlock.getConnectionData(blockView, pos, state).getAllRadii(); + twigRadius = branchBlock.getFamily().getPrimaryThickness(); + } + + var emitter = context.getEmitter(); + + boolean branchesAround = connections[2] + connections[3] + connections[4] + connections[5] != 0; + + int radiusIndex = coreRadius - 9; + if (radiusIndex < 0 || radiusIndex >= trunksBarkQuads.length) return; + + for (Direction face : Direction.values()) { + List barkQuads = trunksBarkQuads[radiusIndex]; + if (barkQuads != null) { + for (BakedQuad quad : barkQuads) { + if (quad.getDirection() == face) { + emitter.fromVanilla(quad, null, face); + emitter.emit(); + } + } + } + + if (face == Direction.UP || face == Direction.DOWN) { + if (connections[face.get3DDataValue()] < twigRadius && !branchesAround) { + List ringQuads = trunksTopRingsQuads[radiusIndex]; + if (ringQuads != null) { + for (BakedQuad quad : ringQuads) { + if (quad.getDirection() == face) { + emitter.fromVanilla(quad, null, face); + emitter.emit(); + } + } + } + } else if (connections[face.get3DDataValue()] < coreRadius) { + List topBarkQuads = trunksTopBarkQuads[radiusIndex]; + if (topBarkQuads != null) { + for (BakedQuad quad : topBarkQuads) { + if (quad.getDirection() == face) { + emitter.fromVanilla(quad, null, face); + emitter.emit(); + } + } + } + } + } + } + } +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricConfigHelper.java b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricConfigHelper.java index 5b3523467..ea2be94ef 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricConfigHelper.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricConfigHelper.java @@ -33,7 +33,21 @@ public Integer getIntConfig(String config){ } @Override public Double getDoubleConfig(String config){ - return DTConfigs.CONFIG.getOrDefault(config, getConfig(config, Double.class)); + if (!DTConfigs.CONFIG.containsConfig(config)){ + DynamicTrees.LOG.error("Failed to get configuration \"{}\" of {} as it does not exist.", config, Double.class); + return null; + } + Pair, ?> def = DTConfigs.getDefaultValue(config); + Double defaultVal; + if (def.getFirst().equals(Float.class)) { + defaultVal = ((Float) def.getSecond()).doubleValue(); + } else if (def.getFirst().equals(Double.class)) { + defaultVal = (Double) def.getSecond(); + } else { + DynamicTrees.LOG.error("Failed to get configuration \"{}\" of {} as it is of {} instead.", config, Double.class, def.getFirst()); + return null; + } + return DTConfigs.CONFIG.getOrDefault(config, defaultVal); } @Override public String getStringConfig(String config){ diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricEventHelper.java b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricEventHelper.java index cfe6725eb..a6f34fb78 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricEventHelper.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricEventHelper.java @@ -23,48 +23,38 @@ public class FabricEventHelper implements IEventHelper { @Override public > void postRegistryEvent(AbstractRegistry registry) { - //RegistryEvent.EVENT.invoker(). } @Override public > void postTypedRegistryEvent(TypedRegistry registry) { - } @Override public void postAddResourceLoadersEventPre(TreeResourceManager resourceManager) { - } @Override public void postAddResourceLoadersEventPost(TreeResourceManager resourceManager) { - } - @Override public void postJsonDeserializerRegistryEvent() { - } @Override public void postApplierEvent(StagedApplierResourceLoader.ApplierStage stage, PropertyAppliers appliers, String identifier) { - } @Override public void postBiomeEntryApplierEvent(JsonPropertyAppliers appliers, String identifier) { - } @Override public void postCancellationApplierEvent(JsonPropertyAppliers appliers, String identifier) { - } @Override public void postSpeciesPostGenerationEvent(PostGenerationContext context) { - } @Override @@ -74,27 +64,26 @@ public boolean postTransitionSaplingToTreeEvent(Species species, Level level, Bl @Override public boolean canCropGrow(Level level, BlockPos pos, BlockState state, boolean doGrow) { - return false; + return doGrow; } @Override public void cropGrowPost(Level level, BlockPos pos, BlockState state) { - } @Override public Species.BiomeSuitabilityEventResult postBiomeSuitabilityEvent(Level level, Biome biome, Species species, BlockPos pos) { - return null; + return new Species.BiomeSuitabilityEventResult(false, Species.defaultSuitability()); } @Override public Seed.VoluntaryPlantEventResult postSeedVoluntaryPlantEvent(ItemEntity entityItem, Species species, BlockPos pos, boolean willPlant) { - return new Seed.VoluntaryPlantEventResult(false, false); + return new Seed.VoluntaryPlantEventResult(false, willPlant); } @Override public PoissonDiscProvider postPoissonDiscProviderCreateEvent(LevelAccessor level, PoissonDiscProvider poissonDiscProvider) { - return null; + return poissonDiscProvider; } } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricInteractionHelper.java b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricInteractionHelper.java index 64977db76..ed3a3a0a7 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricInteractionHelper.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricInteractionHelper.java @@ -1,5 +1,9 @@ package com.dtteam.dynamictrees.platform; +import com.dtteam.dynamictrees.block.branch.BranchBlock; +import com.dtteam.dynamictrees.block.branch.TrunkShellBlock; +import com.dtteam.dynamictrees.block.sapling.PottedSaplingBlock; +import com.dtteam.dynamictrees.block.soil.SoilBlock; import com.dtteam.dynamictrees.item.Seed; import com.dtteam.dynamictrees.platform.services.IInteractionHelper; import net.minecraft.core.BlockPos; @@ -8,6 +12,7 @@ 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.state.BlockState; import net.minecraft.world.level.material.FluidState; @@ -32,6 +37,16 @@ public int setSeedItemEntityLifespan(ItemEntity entityItem, Seed seed) { @Override public boolean blockDestroyByPlayer(BlockState state, Level level, BlockPos pos, Player player, boolean willHarvest, FluidState fluidState) { - return false; + Block block = state.getBlock(); + if (block instanceof BranchBlock branchBlock) { + return branchBlock.onDestroyedByPlayer(state, level, pos, player, willHarvest, fluidState); + } else if (block instanceof TrunkShellBlock trunkShellBlock) { + return trunkShellBlock.onDestroyedByPlayer(state, level, pos, player, willHarvest, fluidState); + } else if (block instanceof SoilBlock soilBlock) { + return soilBlock.onDestroyedByPlayer(state, level, pos, player, willHarvest, fluidState); + } else if (block instanceof PottedSaplingBlock pottedSaplingBlock) { + return pottedSaplingBlock.onDestroyedByPlayer(state, level, pos, player, willHarvest, fluidState); + } + return true; } } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricMiscHelper.java b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricMiscHelper.java index 1e1637815..a3c2ea113 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricMiscHelper.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricMiscHelper.java @@ -1,24 +1,49 @@ package com.dtteam.dynamictrees.platform; +import com.dtteam.dynamictrees.DynamicTrees; import com.dtteam.dynamictrees.entity.FallingTreeEntity; import com.dtteam.dynamictrees.model.FallingTreeEntityModel; +import com.dtteam.dynamictrees.model.FallingTreeEntityModelFabric; import com.dtteam.dynamictrees.platform.services.IMiscHelper; +import com.dtteam.dynamictrees.tree.species.Species; import com.dtteam.dynamictrees.worldgen.IDTBiomeHolderSet; import com.dtteam.dynamictrees.worldgen.holderset.DTBiomeHolderSet; +import com.mojang.blaze3d.platform.NativeImage; +import net.minecraft.client.renderer.texture.SpriteContents; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.server.MinecraftServer; import net.minecraft.world.level.Level; public class FabricMiscHelper implements IMiscHelper { + public static void debugSpeciesRegistry() { + DynamicTrees.LOG.info("=== Species Registry Debug ==="); + DynamicTrees.LOG.info("Total species count: {}", Species.REGISTRY.getAll().size()); + Species cherry = Species.REGISTRY.get(DynamicTrees.location("cherry")); + DynamicTrees.LOG.info("Cherry species: {} (valid: {})", cherry, cherry != null && cherry.isValid()); + Species oak = Species.REGISTRY.get(DynamicTrees.location("oak")); + DynamicTrees.LOG.info("Oak species: {} (valid: {})", oak, oak != null && oak.isValid()); + DynamicTrees.LOG.info("=== End Species Registry Debug ==="); + } + @Override public int getPixelRGBA(TextureAtlasSprite sprite, int x, int y) { - return 0; + try { + SpriteContents contents = sprite.contents(); + NativeImage image = contents.originalImage; + if (image != null) { + return image.getPixelRGBA(x, y); + } + return 0; + } catch (Exception e) { + DynamicTrees.LOG.warn("Failed to get pixel from sprite: {}", e.getMessage()); + return 0; + } } @Override public FallingTreeEntityModel newFallingTreeEntityModel(FallingTreeEntity entity) { - return new FallingTreeEntityModel(entity); + return new FallingTreeEntityModelFabric(entity); } @Override diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/registry/FabricRegistryHandler.java b/fabric/src/main/java/com/dtteam/dynamictrees/registry/FabricRegistryHandler.java index b15fad23d..659c3ff68 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/registry/FabricRegistryHandler.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/registry/FabricRegistryHandler.java @@ -4,7 +4,6 @@ import net.minecraft.core.*; import net.minecraft.core.Registry; import net.minecraft.core.registries.*; -import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.Item; import net.minecraft.world.level.block.Block; @@ -35,6 +34,7 @@ public FabricRegistryHandler() { public FabricRegistryHandler(String modId) { super(ResourceLocation.fromNamespaceAndPath(modId, modId)); + RegistryHandler.REGISTRY.register(this); } /** @@ -64,22 +64,34 @@ private boolean warnIfInvalid(final String type, final ResourceLocation registry @Override public @Nullable Supplier getBlock(ResourceLocation registryName) { - return null; + Block block = BuiltInRegistries.BLOCK.get(registryName); + return () -> block; } @Override public @Nullable Supplier getItem(ResourceLocation registryName) { - return null; + Item item = BuiltInRegistries.ITEM.get(registryName); + return () -> item; } @Override + @SuppressWarnings("unchecked") public Supplier putBlock(ResourceLocation registryName, Supplier blockSup) { - return ()->(T)Registry.register(BuiltInRegistries.BLOCK, registryName, (Block)blockSup.get()); + if (this.warnIfInvalid("Block", registryName)) { + return (Supplier) getBlock(registryName); + } + T block = Registry.register(BuiltInRegistries.BLOCK, registryName, blockSup.get()); + return () -> block; } @Override + @SuppressWarnings("unchecked") public Supplier putItem(ResourceLocation registryName, Supplier itemSup) { - return ()->(T)Registry.register(BuiltInRegistries.ITEM, registryName, (Item)itemSup.get()); + if (this.warnIfInvalid("Item", registryName)) { + return (Supplier) getItem(registryName); + } + T item = Registry.register(BuiltInRegistries.ITEM, registryName, itemSup.get()); + return () -> item; } public static class RegisterEventHandler { diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/treepack/FabricModFileContainer.java b/fabric/src/main/java/com/dtteam/dynamictrees/treepack/FabricModFileContainer.java index 257139ac5..c102beb30 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/treepack/FabricModFileContainer.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/treepack/FabricModFileContainer.java @@ -2,7 +2,9 @@ import net.fabricmc.loader.api.ModContainer; +import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; import java.util.Optional; public class FabricModFileContainer extends ModFileContainer { @@ -15,7 +17,14 @@ public FabricModFileContainer(ModContainer modContainer) { @Override public Optional findResource(String strings) { - return modContainer.findPath(strings); + List rootPaths = modContainer.getRootPaths(); + for (Path rootPath : rootPaths) { + Path resourcePath = rootPath.resolve(strings); + if (Files.exists(resourcePath)) { + return Optional.of(resourcePath); + } + } + return Optional.empty(); } @Override diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/FabricBiomeModifications.java b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/FabricBiomeModifications.java new file mode 100644 index 000000000..c55e6742f --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/FabricBiomeModifications.java @@ -0,0 +1,121 @@ +package com.dtteam.dynamictrees.worldgen; + +import com.dtteam.dynamictrees.DynamicTrees; +import com.dtteam.dynamictrees.api.worldgen.BiomePropertySelectors; +import com.dtteam.dynamictrees.api.worldgen.FeatureCanceller; +import com.dtteam.dynamictrees.platform.Services; +import com.dtteam.dynamictrees.platform.services.IConfigHelper; +import com.dtteam.dynamictrees.registry.DTRegistries; +import com.dtteam.dynamictrees.worldgen.featurecancellation.FeatureCancellationRegistry; +import net.fabricmc.fabric.api.biome.v1.BiomeModificationContext; +import net.fabricmc.fabric.api.biome.v1.BiomeModifications; +import net.fabricmc.fabric.api.biome.v1.BiomeSelectionContext; +import net.fabricmc.fabric.api.biome.v1.BiomeSelectors; +import net.fabricmc.fabric.api.biome.v1.ModificationPhase; +import net.minecraft.core.Holder; +import net.minecraft.core.HolderSet; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.levelgen.GenerationStep; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class FabricBiomeModifications { + + private static final ResourceLocation REMOVE_TREES_ID = DynamicTrees.location("remove_vanilla_trees"); + private static final ResourceLocation ADD_TREES_ID = DynamicTrees.location("add_dynamic_trees"); + public static final TagKey FEATURE_CANCELLER_EXCLUSIONS_KEY = TagKey.create( + net.minecraft.core.registries.Registries.PLACED_FEATURE, + DynamicTrees.location("feature_canceller_exclusions")); + + public static void register() { + BiomeModifications.create(REMOVE_TREES_ID) + .add(ModificationPhase.REMOVALS, BiomeSelectors.all(), (selectionContext, modificationContext) -> { + if (!Services.CONFIG.getBoolConfig(IConfigHelper.WORLD_GEN)) { + return; + } + removeVanillaTrees(selectionContext, modificationContext); + }); + + BiomeModifications.create(ADD_TREES_ID) + .add(ModificationPhase.ADDITIONS, BiomeSelectors.all(), (selectionContext, modificationContext) -> { + if (!Services.CONFIG.getBoolConfig(IConfigHelper.WORLD_GEN)) { + return; + } + addDynamicTrees(modificationContext); + }); + } + + private static void removeVanillaTrees(BiomeSelectionContext selectionContext, BiomeModificationContext context) { + ResourceKey biomeKey = selectionContext.getBiomeKey(); + + BiomePropertySelectors.NormalFeatureCancellation featureCancellations = new BiomePropertySelectors.NormalFeatureCancellation(); + + for (FeatureCancellationRegistry.Entry entry : FeatureCancellationRegistry.getCancellations()) { + if (entry.biomes().containsKey(biomeKey)) { + if (entry.operation() == BiomeDatabase.Operation.REPLACE) { + featureCancellations.reset(); + } + featureCancellations.addFrom(entry.cancellations()); + } + } + + List> featuresToRemove = new ArrayList<>(); + + for (GenerationStep.Decoration stage : featureCancellations.getDecorationSteps()) { + int stageIndex = stage.ordinal(); + List> features = selectionContext.getBiomeRegistryEntry() + .value() + .getGenerationSettings() + .features(); + + if (stageIndex >= features.size()) { + continue; + } + + HolderSet stageFeatures = features.get(stageIndex); + + for (Holder placedFeatureHolder : stageFeatures) { + if (placedFeatureHolder.is(FEATURE_CANCELLER_EXCLUSIONS_KEY)) { + continue; + } + + PlacedFeature placedFeature = placedFeatureHolder.value(); + + boolean shouldCancel = placedFeature.getFeatures().anyMatch(configuredFeature -> { + for (FeatureCanceller featureCanceller : featureCancellations.getCancellers()) { + if (featureCanceller.shouldCancel(configuredFeature, featureCancellations)) { + return true; + } + } + return false; + }); + + if (shouldCancel) { + Optional> keyOpt = selectionContext.getPlacedFeatureKey(placedFeature); + keyOpt.ifPresent(featuresToRemove::add); + } + } + } + + for (ResourceKey key : featuresToRemove) { + context.getGenerationSettings().removeFeature(key); + } + } + + private static void addDynamicTrees(BiomeModificationContext context) { + context.getGenerationSettings().addFeature( + GenerationStep.Decoration.VEGETAL_DECORATION, + DTRegistries.CAVE_ROOTED_TREE_PLACED_FEATURE + ); + context.getGenerationSettings().addFeature( + GenerationStep.Decoration.VEGETAL_DECORATION, + DTRegistries.DYNAMIC_TREE_PLACED_FEATURE + ); + } +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/DTBiomeHolderSet.java b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/DTBiomeHolderSet.java index 7efdc8791..b108b8ad7 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/DTBiomeHolderSet.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/DTBiomeHolderSet.java @@ -12,52 +12,94 @@ import net.minecraft.world.level.biome.Biome; import org.jetbrains.annotations.NotNull; -import java.util.Iterator; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.function.Supplier; import java.util.stream.Stream; public class DTBiomeHolderSet implements IDTBiomeHolderSet { + private final List> includeComponents = new ArrayList<>(); + private final List> excludeComponents = new ArrayList<>(); + + private Set> getSet() { + Set> tempSet = null; + for (HolderSet component : this.includeComponents) { + if (tempSet == null) { + tempSet = new HashSet<>(); + component.forEach(tempSet::add); + } else { + Set> componentSet = new HashSet<>(); + component.forEach(componentSet::add); + tempSet.retainAll(componentSet); + } + } + if (tempSet == null) { + tempSet = new HashSet<>(); + } + for (HolderSet component : this.excludeComponents) { + component.forEach(tempSet::remove); + } + return tempSet; + } + @Override public boolean containsKey(ResourceKey biomeKey) { + Set> currentSet = this.getSet(); + for (Holder holder : currentSet) { + Optional> key = holder.unwrapKey(); + if (key.isPresent() && key.get().equals(biomeKey)) { + return true; + } + } return false; } @Override public Stream> stream() { - return Stream.empty(); + return this.getSet().stream(); } @Override public int size() { - return 0; + return this.getSet().size(); } @Override public Either, List>> unwrap() { - return null; + return Either.right(new ArrayList<>(this.getSet())); } @Override public Optional> getRandomElement(RandomSource random) { - return Optional.empty(); + Set> set = this.getSet(); + if (set.isEmpty()) { + return Optional.empty(); + } + int index = random.nextInt(set.size()); + Iterator> iterator = set.iterator(); + for (int i = 0; i < index; i++) { + iterator.next(); + } + return Optional.of(iterator.next()); } @Override public Holder get(int index) { - return null; + Iterator> iterator = this.getSet().iterator(); + for (int i = 0; i < index; i++) { + iterator.next(); + } + return iterator.next(); } @Override public boolean contains(Holder holder) { - return false; + return this.getSet().contains(holder); } @Override public boolean canSerializeIn(HolderOwner owner) { - return false; + return true; } @Override @@ -67,47 +109,50 @@ public Optional> unwrapKey() { @Override public List> getIncludeComponents() { - return List.of(); + return this.includeComponents; } @Override public List> getExcludeComponents() { - return List.of(); + return this.excludeComponents; } @Override - public void addHolderSet(List> components, HolderSet holderSetSupplier) { - + public void addHolderSet(List> components, HolderSet holderSet) { + components.add(holderSet); } @Override public void addDelayedHolderSet(List> components, Supplier> holderSetSupplier) { - + components.add(new DelayedHolderSet<>(holderSetSupplier)); } @Override public void addNameRegexMatch(List> components, Supplier> registryLookup, String regex) { - + Supplier> sup = () -> new NameRegexMatchHolderSet<>(registryLookup.get(), regex); + addDelayedHolderSet(components, sup); } @Override public void addTagsRegexMatch(List> components, Supplier> registryLookup, String regex) { - + Supplier> sup = () -> new TagsRegexMatchHolderSet<>(registryLookup.get(), regex); + addDelayedHolderSet(components, sup); } @Override public void addOr(List> components, List> values) { - + addHolderSet(components, new OrHolderSet<>(values)); } @Override public void clear() { - + this.includeComponents.clear(); + this.excludeComponents.clear(); } @NotNull @Override public Iterator> iterator() { - return null; + return this.getSet().iterator(); } } \ No newline at end of file diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/DelayedHolderSet.java b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/DelayedHolderSet.java new file mode 100644 index 000000000..c70ab944e --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/DelayedHolderSet.java @@ -0,0 +1,81 @@ +package com.dtteam.dynamictrees.worldgen.holderset; + +import com.mojang.datafixers.util.Either; +import net.minecraft.core.Holder; +import net.minecraft.core.HolderOwner; +import net.minecraft.core.HolderSet; +import net.minecraft.tags.TagKey; +import net.minecraft.util.RandomSource; +import org.jetbrains.annotations.NotNull; + +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Stream; + +public class DelayedHolderSet implements HolderSet { + private final Supplier> holderSetSupplier; + + public DelayedHolderSet(Supplier> holderSetSupplier) { + this.holderSetSupplier = holderSetSupplier; + } + + @Override + public Stream> stream() { + return this.holderSetSupplier.get().stream(); + } + + @Override + public int size() { + return this.holderSetSupplier.get().size(); + } + + @Override + public Either, List>> unwrap() { + return this.holderSetSupplier.get().unwrap(); + } + + @Override + public Optional> getRandomElement(RandomSource random) { + return this.holderSetSupplier.get().getRandomElement(random); + } + + @Override + public Holder get(int index) { + return this.holderSetSupplier.get().get(index); + } + + @Override + public boolean contains(Holder holder) { + return this.holderSetSupplier.get().contains(holder); + } + + @Override + public boolean canSerializeIn(HolderOwner owner) { + return this.holderSetSupplier.get().canSerializeIn(owner); + } + + @Override + public Optional> unwrapKey() { + return this.holderSetSupplier.get().unwrapKey(); + } + + @NotNull + @Override + public Iterator> iterator() { + return this.holderSetSupplier.get().iterator(); + } + + @Override + public void forEach(Consumer> action) { + this.holderSetSupplier.get().forEach(action); + } + + @Override + public Spliterator> spliterator() { + return this.holderSetSupplier.get().spliterator(); + } +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/NameRegexMatchHolderSet.java b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/NameRegexMatchHolderSet.java new file mode 100644 index 000000000..f0c897065 --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/NameRegexMatchHolderSet.java @@ -0,0 +1,18 @@ +package com.dtteam.dynamictrees.worldgen.holderset; + +import net.minecraft.core.Holder; +import net.minecraft.core.HolderLookup; + +import java.util.stream.Stream; + +public class NameRegexMatchHolderSet extends RegexMatchHolderSet { + + public NameRegexMatchHolderSet(HolderLookup.RegistryLookup registryLookup, String regex) { + super(registryLookup, regex); + } + + @Override + protected Stream getInput(Holder holder) { + return holder.unwrapKey().stream().map(key -> key.location().toString()); + } +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/OrHolderSet.java b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/OrHolderSet.java new file mode 100644 index 000000000..279f2d5db --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/OrHolderSet.java @@ -0,0 +1,27 @@ +package com.dtteam.dynamictrees.worldgen.holderset; + +import net.minecraft.core.Holder; +import net.minecraft.core.HolderOwner; +import net.minecraft.core.HolderSet; + +import java.util.List; +import java.util.stream.Stream; + +public class OrHolderSet extends StreamBackedHolderSet { + + private final List> values; + + public OrHolderSet(List> values) { + this.values = values; + } + + @Override + public Stream> stream() { + return this.values.stream().flatMap(HolderSet::stream).distinct(); + } + + @Override + public boolean canSerializeIn(HolderOwner owner) { + return this.values.stream().allMatch(set -> set.canSerializeIn(owner)); + } +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/RegexMatchHolderSet.java b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/RegexMatchHolderSet.java new file mode 100644 index 000000000..eefc08c7c --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/RegexMatchHolderSet.java @@ -0,0 +1,49 @@ +package com.dtteam.dynamictrees.worldgen.holderset; + +import net.minecraft.core.Holder; +import net.minecraft.core.HolderLookup; +import net.minecraft.core.HolderOwner; + +import java.util.regex.Pattern; +import java.util.stream.Stream; + +public abstract class RegexMatchHolderSet extends StreamBackedHolderSet { + + private final HolderLookup.RegistryLookup registryLookup; + private final String regex; + private Pattern pattern; + + public RegexMatchHolderSet(HolderLookup.RegistryLookup registryLookup, String regex) { + this.registryLookup = registryLookup; + this.regex = regex; + } + + public final HolderLookup.RegistryLookup registryLookup() { + return this.registryLookup; + } + + public final String regex() { + return this.regex; + } + + private Pattern getPattern() { + if (this.pattern == null) { + this.pattern = Pattern.compile(this.regex); + } + return this.pattern; + } + + @SuppressWarnings("unchecked") + @Override + public Stream> stream() { + return (Stream>) (Stream) this.registryLookup.listElements() + .filter(holder -> this.getInput(holder).anyMatch(input -> this.getPattern().matcher(input).matches())); + } + + @Override + public boolean canSerializeIn(HolderOwner owner) { + return this.registryLookup.canSerializeIn(owner); + } + + protected abstract Stream getInput(Holder holder); +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/StreamBackedHolderSet.java b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/StreamBackedHolderSet.java new file mode 100644 index 000000000..a4b7ad1e0 --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/StreamBackedHolderSet.java @@ -0,0 +1,61 @@ +package com.dtteam.dynamictrees.worldgen.holderset; + +import com.mojang.datafixers.util.Either; +import net.minecraft.Util; +import net.minecraft.core.Holder; +import net.minecraft.core.HolderSet; +import net.minecraft.tags.TagKey; +import net.minecraft.util.RandomSource; + +import java.util.*; +import java.util.stream.Collectors; + +public abstract class StreamBackedHolderSet implements HolderSet { + public List> contents() { + return this.stream().collect(Collectors.toList()); + } + + public Set> contentsSet() { + return this.stream().collect(Collectors.toSet()); + } + + @Override + public int size() { + return this.contents().size(); + } + + @Override + public Spliterator> spliterator() { + return this.stream().spliterator(); + } + + @Override + public Iterator> iterator() { + return this.stream().iterator(); + } + + @Override + public Optional> getRandomElement(RandomSource random) { + return Util.getRandomSafe(this.contents(), random); + } + + @Override + public Holder get(int index) { + return this.contents().get(index); + } + + @Override + public Either, List>> unwrap() { + return Either.right(this.contents()); + } + + @Override + public boolean contains(Holder holder) { + return this.stream().anyMatch(h -> Objects.equals(h, holder)); + } + + @Override + public Optional> unwrapKey() { + return Optional.empty(); + } +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/TagsRegexMatchHolderSet.java b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/TagsRegexMatchHolderSet.java new file mode 100644 index 000000000..8dbc0cc2c --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/holderset/TagsRegexMatchHolderSet.java @@ -0,0 +1,18 @@ +package com.dtteam.dynamictrees.worldgen.holderset; + +import net.minecraft.core.Holder; +import net.minecraft.core.HolderLookup; + +import java.util.stream.Stream; + +public class TagsRegexMatchHolderSet extends RegexMatchHolderSet { + + public TagsRegexMatchHolderSet(HolderLookup.RegistryLookup registryLookup, String regex) { + super(registryLookup, regex); + } + + @Override + protected Stream getInput(Holder holder) { + return holder.tags().map(tagKey -> tagKey.location().toString()); + } +} diff --git a/fabric/src/main/resources/dynamictrees.fabric.mixins.json b/fabric/src/main/resources/dynamictrees.fabric.mixins.json index bf37b7228..18e11a145 100644 --- a/fabric/src/main/resources/dynamictrees.fabric.mixins.json +++ b/fabric/src/main/resources/dynamictrees.fabric.mixins.json @@ -7,10 +7,15 @@ "mixins": [ "MixinBrewingStandBlockEntity", "MixinBrewingStandMenu", + "MixinBushBlock", "MixinChunkSerializer", - "MixinPotionBrewing" + "MixinDynamicTreeFeature", + "MixinMinecraftServer", + "MixinPotionBrewing", + "MixinSaplingBlock" + ], + "client": [ ], - "client": [], "server": [], "injectors": { "defaultRequire": 1 diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index db402c50f..654ae79b8 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -8,8 +8,8 @@ "${mod_author}" ], "contact": { - "homepage": "https://fabricmc.net/", - "sources": "https://github.com/FabricMC/fabric-example-mod" + "homepage": "https://www.curseforge.com/minecraft/mc-mods/dynamictrees", + "sources": "https://github.com/DynamicTreesTeam/DynamicTrees" }, "license": "${license}", "icon": "${mod_id}.png", @@ -17,6 +17,9 @@ "entrypoints": { "main": [ "com.dtteam.dynamictrees.DynamicTreesFabric" + ], + "client": [ + "com.dtteam.dynamictrees.DynamicTreesFabricClient" ] }, "mixins": [ @@ -30,7 +33,7 @@ "java": ">=${java_version}" }, "suggests": { - "another-mod": "*" + "dynamictreesplus": "*" } } \ No newline at end of file From ec2d636d5e6521c7e37edd1e6f883dc560b6feaf Mon Sep 17 00:00:00 2001 From: Nyfaria DragonMoose Date: Sat, 24 Jan 2026 13:23:47 -0600 Subject: [PATCH 4/9] possibly done --- build.gradle | 15 ------ .../dynamictrees/client/TextureHelper.java | 4 +- .../FallingTreeEntityModelTrackerCache.java | 4 +- .../dynamictrees/platform/ClientServices.java | 20 +++++++ .../platform/services/IClientHelper.java | 15 ++++++ .../platform/services/IMiscHelper.java | 9 +--- .../model/DTModelLoadingPlugin.java | 54 +++++++++++++++---- .../baked/BasicBranchBlockBakedModel.java | 16 +++--- .../baked/BasicRootsBlockBakedModel.java | 34 +++++++----- .../baked/SurfaceRootBlockBakedModel.java | 6 +-- .../platform/FabricClientHelper.java | 34 ++++++++++++ .../platform/FabricMiscHelper.java | 25 --------- ...namictrees.platform.services.IClientHelper | 1 + .../platform/NeoForgeClientHelper.java | 27 ++++++++++ .../platform/NeoForgeMiscHelper.java | 19 ------- ...namictrees.platform.services.IClientHelper | 1 + 16 files changed, 179 insertions(+), 105 deletions(-) create mode 100644 common/src/main/java/com/dtteam/dynamictrees/platform/ClientServices.java create mode 100644 common/src/main/java/com/dtteam/dynamictrees/platform/services/IClientHelper.java create mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricClientHelper.java create mode 100644 fabric/src/main/resources/META-INF/services/com.dtteam.dynamictrees.platform.services.IClientHelper create mode 100644 neoforge/src/main/java/com/dtteam/dynamictrees/platform/NeoForgeClientHelper.java create mode 100644 neoforge/src/main/resources/META-INF/services/com.dtteam.dynamictrees.platform.services.IClientHelper diff --git a/build.gradle b/build.gradle index 61fdb0717..395289a89 100644 --- a/build.gradle +++ b/build.gradle @@ -3,19 +3,4 @@ plugins { id 'fabric-loom' version '1.9-SNAPSHOT' apply false // see https://projects.neoforged.net/neoforged/moddevgradle for new versions id 'net.neoforged.moddev' version '2.0.49-beta' apply false - id 'com.matyrobbrt.mc.registrationutils' version "${regutils_version}" } -registrationUtils { - group "com.dtteam.${mod_id}.registration" - projects { - fabric { - type 'fabric' - } - neoforge { - type 'neoforge' - } - common { - type 'common' - } - } -} \ No newline at end of file diff --git a/common/src/main/java/com/dtteam/dynamictrees/client/TextureHelper.java b/common/src/main/java/com/dtteam/dynamictrees/client/TextureHelper.java index f11b8db2c..8add702d2 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/client/TextureHelper.java +++ b/common/src/main/java/com/dtteam/dynamictrees/client/TextureHelper.java @@ -1,6 +1,6 @@ package com.dtteam.dynamictrees.client; -import com.dtteam.dynamictrees.platform.Services; +import com.dtteam.dynamictrees.platform.ClientServices; import com.mojang.blaze3d.platform.NativeImage; import net.minecraft.client.renderer.texture.TextureAtlasSprite; @@ -37,7 +37,7 @@ public PixelBuffer(TextureAtlasSprite sprite) { pixels = new int[w * h]; for (int x = 0; x < w; x++) { for (int y = 0; y < h; y++) { - pixels[calcPos(x, y)] = Services.MISC.getPixelRGBA(sprite, x, y); + pixels[calcPos(x, y)] = ClientServices.CLIENT.getPixelRGBA(sprite, x, y); } } diff --git a/common/src/main/java/com/dtteam/dynamictrees/model/FallingTreeEntityModelTrackerCache.java b/common/src/main/java/com/dtteam/dynamictrees/model/FallingTreeEntityModelTrackerCache.java index f382750e6..475b9e95c 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/model/FallingTreeEntityModelTrackerCache.java +++ b/common/src/main/java/com/dtteam/dynamictrees/model/FallingTreeEntityModelTrackerCache.java @@ -1,7 +1,7 @@ package com.dtteam.dynamictrees.model; import com.dtteam.dynamictrees.entity.FallingTreeEntity; -import com.dtteam.dynamictrees.platform.Services; +import com.dtteam.dynamictrees.platform.ClientServices; import net.minecraft.world.level.Level; import org.jetbrains.annotations.Nullable; @@ -17,7 +17,7 @@ public class FallingTreeEntityModelTrackerCache { @Nullable public static FallingTreeEntityModel getOrCreateModel(FallingTreeEntity entity) { if (entity.level().isClientSide()) - return models.computeIfAbsent(entity.getId(), i -> Services.MISC.newFallingTreeEntityModel(entity)); + return models.computeIfAbsent(entity.getId(), i -> ClientServices.CLIENT.newFallingTreeEntityModel(entity)); return null; } diff --git a/common/src/main/java/com/dtteam/dynamictrees/platform/ClientServices.java b/common/src/main/java/com/dtteam/dynamictrees/platform/ClientServices.java new file mode 100644 index 000000000..2dacb9f3c --- /dev/null +++ b/common/src/main/java/com/dtteam/dynamictrees/platform/ClientServices.java @@ -0,0 +1,20 @@ +package com.dtteam.dynamictrees.platform; + +import com.dtteam.dynamictrees.DynamicTrees; +import com.dtteam.dynamictrees.platform.services.IClientHelper; + +import java.util.ServiceLoader; + + +public class ClientServices { + + public static final IClientHelper CLIENT = load(IClientHelper.class); + + public static T load(Class clazz) { + final T loadedService = ServiceLoader.load(clazz) + .findFirst() + .orElseThrow(() -> new NullPointerException("Failed to load service for " + clazz.getName())); + DynamicTrees.LOG.debug("Loaded {} for service {}", loadedService, clazz); + return loadedService; + } +} diff --git a/common/src/main/java/com/dtteam/dynamictrees/platform/services/IClientHelper.java b/common/src/main/java/com/dtteam/dynamictrees/platform/services/IClientHelper.java new file mode 100644 index 000000000..fd6b025dc --- /dev/null +++ b/common/src/main/java/com/dtteam/dynamictrees/platform/services/IClientHelper.java @@ -0,0 +1,15 @@ +package com.dtteam.dynamictrees.platform.services; + +import com.dtteam.dynamictrees.entity.FallingTreeEntity; +import com.dtteam.dynamictrees.model.FallingTreeEntityModel; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; + + +public interface IClientHelper { + + int getPixelRGBA(TextureAtlasSprite sprite, int x, int y); + + + FallingTreeEntityModel newFallingTreeEntityModel(FallingTreeEntity entity); + +} diff --git a/common/src/main/java/com/dtteam/dynamictrees/platform/services/IMiscHelper.java b/common/src/main/java/com/dtteam/dynamictrees/platform/services/IMiscHelper.java index b0bd7ad5c..d00377915 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/platform/services/IMiscHelper.java +++ b/common/src/main/java/com/dtteam/dynamictrees/platform/services/IMiscHelper.java @@ -1,19 +1,12 @@ package com.dtteam.dynamictrees.platform.services; -import com.dtteam.dynamictrees.entity.FallingTreeEntity; -import com.dtteam.dynamictrees.model.FallingTreeEntityModel; import com.dtteam.dynamictrees.worldgen.IDTBiomeHolderSet; -import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.server.MinecraftServer; import net.minecraft.world.level.Level; public interface IMiscHelper { - int getPixelRGBA (TextureAtlasSprite sprite, int x, int y); - - FallingTreeEntityModel newFallingTreeEntityModel (FallingTreeEntity entity); - - boolean isLevelRestoringBlockSnapshots (Level level); + boolean isLevelRestoringBlockSnapshots(Level level); MinecraftServer getCurrentServer(); diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/DTModelLoadingPlugin.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/DTModelLoadingPlugin.java index b8c83595e..752b58e5e 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/model/DTModelLoadingPlugin.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/DTModelLoadingPlugin.java @@ -130,6 +130,8 @@ private void initBranchModels(Function spriteGette BakedModel model = createRootsBlockModel(barkTexture, ringsTexture, spriteGetter); UNDERGROUND_ROOTS_MODEL_CACHE.put(blockId.withSuffix("_filled"), model); }); + + }); } } @@ -159,6 +161,25 @@ private BakedModel createRootsBlockModel(ResourceLocation barkTexture, ResourceL return new BasicRootsBlockBakedModel(barkSprite, ringsSprite); } + private BakedModel createFallbackRootsModel(UndergroundRootsFamily family, String variant, Function spriteGetter) { + if (variant.contains("layer=exposed")) { + return family.getPrimitiveRoots().map(primitiveRoots -> { + ResourceLocation primitiveRootsId = BuiltInRegistries.BLOCK.getKey(primitiveRoots); + ResourceLocation barkTexture = ResourceLocation.fromNamespaceAndPath(primitiveRootsId.getNamespace(), "block/" + primitiveRootsId.getPath() + "_side"); + ResourceLocation ringsTexture = ResourceLocation.fromNamespaceAndPath(primitiveRootsId.getNamespace(), "block/" + primitiveRootsId.getPath() + "_top"); + return createRootsBlockModel(barkTexture, ringsTexture, spriteGetter); + }).orElse(null); + } else if (variant.contains("layer=filled")) { + return family.getPrimitiveFilledRoots().map(primitiveFilledRoots -> { + ResourceLocation primitiveFilledRootsId = BuiltInRegistries.BLOCK.getKey(primitiveFilledRoots); + ResourceLocation barkTexture = ResourceLocation.fromNamespaceAndPath(primitiveFilledRootsId.getNamespace(), "block/" + primitiveFilledRootsId.getPath() + "_side"); + ResourceLocation ringsTexture = ResourceLocation.fromNamespaceAndPath(primitiveFilledRootsId.getNamespace(), "block/" + primitiveFilledRootsId.getPath() + "_top"); + return createRootsBlockModel(barkTexture, ringsTexture, spriteGetter); + }).orElse(null); + } + return null; + } + private BakedModel modifyModelAfterBake(BakedModel model, ModelModifier.AfterBake.Context context) { ModelResourceLocation modelId = context.topLevelId(); if (modelId == null) return model; @@ -170,8 +191,8 @@ private BakedModel modifyModelAfterBake(BakedModel model, ModelModifier.AfterBak ResourceLocation blockId = modelId.id(); Block block = BuiltInRegistries.BLOCK.get(blockId); - if (block instanceof BasicRootsBlock) { - initBranchModels(material -> context.textureGetter().apply(new Material(material.atlasLocation(), material.texture()))); + if (block instanceof BasicRootsBlock rootsBlock) { + initBranchModels(context.textureGetter()); String variant = modelId.variant(); ResourceLocation cacheKey; @@ -179,6 +200,8 @@ private BakedModel modifyModelAfterBake(BakedModel model, ModelModifier.AfterBak cacheKey = blockId.withSuffix("_filled"); } else if (variant.contains("layer=exposed")) { cacheKey = blockId.withSuffix("_exposed"); + } else if (variant.contains("layer=covered")) { + return model; } else { return model; } @@ -187,26 +210,37 @@ private BakedModel modifyModelAfterBake(BakedModel model, ModelModifier.AfterBak if (rootsModel != null) { return rootsModel; } - } - - if (block instanceof BranchBlock) { - initBranchModels(material -> context.textureGetter().apply(new Material(material.atlasLocation(), material.texture()))); - BakedModel branchModel = BRANCH_MODEL_CACHE.get(blockId); - if (branchModel != null) { - return branchModel; + if (rootsBlock.getFamily() instanceof UndergroundRootsFamily undergroundFamily) { + BakedModel fallbackModel = createFallbackRootsModel(undergroundFamily, variant, context.textureGetter()); + if (fallbackModel != null) { + UNDERGROUND_ROOTS_MODEL_CACHE.put(cacheKey, fallbackModel); + return fallbackModel; + } } + return model; } if (block instanceof SurfaceRootBlock) { - initBranchModels(material -> context.textureGetter().apply(new Material(material.atlasLocation(), material.texture()))); + initBranchModels(context.textureGetter()); BakedModel rootModel = ROOT_MODEL_CACHE.get(blockId); if (rootModel != null) { return rootModel; } + return model; + } + + if (block instanceof BranchBlock) { + initBranchModels(context.textureGetter()); + + BakedModel branchModel = BRANCH_MODEL_CACHE.get(blockId); + if (branchModel != null) { + return branchModel; + } } + return model; } } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/BasicBranchBlockBakedModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/BasicBranchBlockBakedModel.java index 6e104eb5c..80b4d2e1f 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/BasicBranchBlockBakedModel.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/BasicBranchBlockBakedModel.java @@ -20,7 +20,6 @@ import net.minecraft.core.Direction.Axis; import net.minecraft.core.Direction.AxisDirection; import net.minecraft.util.RandomSource; -import net.minecraft.util.RandomSource; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.BlockAndTintGetter; import net.minecraft.world.level.block.state.BlockState; @@ -39,7 +38,7 @@ public class BasicBranchBlockBakedModel implements BakedModel, FabricBakedModel protected final TextureAtlasSprite barkTexture; protected final TextureAtlasSprite ringsTexture; - public final List[][] sleevesQuads = new List[6][7]; + public final List[][] sleevesQuads = new List[6][8]; public final List[][] coresQuads = new List[3][8]; public final List[] ringsQuads = new List[8]; @@ -201,8 +200,7 @@ public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, Block } for (BakedQuad quad : quads) { if (quad.getDirection() == face) { - emitter.fromVanilla(quad, null, face); - emitter.emit(); + emitQuad(emitter, quad, face); } } } @@ -211,13 +209,12 @@ public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, Block for (Direction connDir : Direction.values()) { int idx = connDir.get3DDataValue(); int connRadius = connections[idx]; - if (connRadius > 0 && (connRadius <= twigRadius || face != connDir)) { + if (connRadius > 0 && connRadius < 8 && (connRadius <= twigRadius || face != connDir)) { List sleeveQuads = sleevesQuads[idx][connRadius - 1]; if (sleeveQuads != null) { for (BakedQuad quad : sleeveQuads) { if (quad.getDirection() == face) { - emitter.fromVanilla(quad, null, face); - emitter.emit(); + emitQuad(emitter, quad, face); } } } @@ -227,6 +224,11 @@ public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, Block } } + protected void emitQuad(QuadEmitter emitter, BakedQuad quad, Direction cullFace) { + emitter.fromVanilla(quad, null, cullFace); + emitter.emit(); + } + @Override public void emitItemQuads(ItemStack stack, Supplier randomSupplier, RenderContext context) { } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/BasicRootsBlockBakedModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/BasicRootsBlockBakedModel.java index 0a8a7d7b7..e900afa56 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/BasicRootsBlockBakedModel.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/BasicRootsBlockBakedModel.java @@ -3,6 +3,7 @@ import com.dtteam.dynamictrees.block.branch.BasicRootsBlock; import com.dtteam.dynamictrees.block.branch.BranchBlock; import com.google.common.collect.Maps; +import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter; import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; import net.minecraft.client.renderer.block.model.BakedQuad; import net.minecraft.client.renderer.block.model.BlockElement; @@ -76,12 +77,20 @@ public List bakeSleeveFace(int radius, Direction dir, TextureAtlasSpr @Override protected int getRadius(BlockState blockState) { - if (blockState.getBlock() instanceof BasicRootsBlock rootsBlock) { - return rootsBlock.getRadius(blockState); + if (blockState.getBlock() instanceof BasicRootsBlock) { + if (blockState.hasProperty(BasicRootsBlock.RADIUS)) { + return blockState.getValue(BasicRootsBlock.RADIUS); + } } return super.getRadius(blockState); } + @Override + protected void emitQuad(QuadEmitter emitter, BakedQuad quad, Direction cullFace) { + emitter.fromVanilla(quad, null, null); + emitter.emit(); + } + @Override public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, BlockPos pos, Supplier randomSupplier, RenderContext context) { if (state == null) return; @@ -109,7 +118,9 @@ public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, Block Direction coreRingDir = (numConnections == 1 && sourceDir != null) ? sourceDir.getOpposite() : null; for (Direction face : Direction.values()) { - if (coreRadius != connections[face.get3DDataValue()]) { + int connectionOnFace = connections[face.get3DDataValue()]; + + if (coreRadius != connectionOnFace) { List quads; if (coreRingDir == null || coreRingDir != face) { quads = coresQuads[coreDir][coreRadius - 1]; @@ -118,8 +129,7 @@ public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, Block } for (BakedQuad quad : quads) { if (quad.getDirection() == face) { - emitter.fromVanilla(quad, null, face); - emitter.emit(); + emitQuad(emitter, quad, face); } } } @@ -128,13 +138,12 @@ public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, Block for (Direction connDir : Direction.values()) { int idx = connDir.get3DDataValue(); int connRadius = connections[idx]; - if (connRadius > 0 && (connRadius <= twigRadius || face != connDir)) { + if (connRadius > 0 && connRadius < 8 && (connRadius <= twigRadius || face != connDir)) { List sleeveQuads = sleevesQuads[idx][connRadius - 1]; if (sleeveQuads != null) { for (BakedQuad quad : sleeveQuads) { if (quad.getDirection() == face) { - emitter.fromVanilla(quad, null, face); - emitter.emit(); + emitQuad(emitter, quad, face); } } } @@ -142,15 +151,12 @@ public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, Block } } - int idx = face.get3DDataValue(); - int connRadius = connections[idx]; - if (connRadius > 0) { - List endFaceQuads = sleeveEndFaces[idx][connRadius - 1]; + if (connectionOnFace > 0 && connectionOnFace <= coreRadius) { + List endFaceQuads = sleeveEndFaces[face.get3DDataValue()][connectionOnFace - 1]; if (endFaceQuads != null) { for (BakedQuad quad : endFaceQuads) { if (quad.getDirection() == face) { - emitter.fromVanilla(quad, null, face); - emitter.emit(); + emitQuad(emitter, quad, face); } } } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/SurfaceRootBlockBakedModel.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/SurfaceRootBlockBakedModel.java index b15ba0b85..b56fe423c 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/SurfaceRootBlockBakedModel.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/baked/SurfaceRootBlockBakedModel.java @@ -254,7 +254,7 @@ public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, Block if (coreQuads != null) { for (BakedQuad quad : coreQuads) { if (quad.getDirection() == face) { - emitter.fromVanilla(quad, null, face); + emitter.fromVanilla(quad, null, null); emitter.emit(); } } @@ -269,7 +269,7 @@ public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, Block if (isGrounded && sleevesQuads[idx][connRadius - 1] != null) { for (BakedQuad quad : sleevesQuads[idx][connRadius - 1]) { if (quad.getDirection() == face) { - emitter.fromVanilla(quad, null, face); + emitter.fromVanilla(quad, null, null); emitter.emit(); } } @@ -277,7 +277,7 @@ public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, Block if (connectionLevels[idx] == RootConnections.ConnectionLevel.HIGH && vertsQuads[idx][connRadius - 1] != null) { for (BakedQuad quad : vertsQuads[idx][connRadius - 1]) { if (quad.getDirection() == face) { - emitter.fromVanilla(quad, null, face); + emitter.fromVanilla(quad, null, null); emitter.emit(); } } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricClientHelper.java b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricClientHelper.java new file mode 100644 index 000000000..eb6690304 --- /dev/null +++ b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricClientHelper.java @@ -0,0 +1,34 @@ +package com.dtteam.dynamictrees.platform; + +import com.dtteam.dynamictrees.DynamicTrees; +import com.dtteam.dynamictrees.entity.FallingTreeEntity; +import com.dtteam.dynamictrees.model.FallingTreeEntityModel; +import com.dtteam.dynamictrees.model.FallingTreeEntityModelFabric; +import com.dtteam.dynamictrees.platform.services.IClientHelper; +import com.mojang.blaze3d.platform.NativeImage; +import net.minecraft.client.renderer.texture.SpriteContents; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; + +public class FabricClientHelper implements IClientHelper { + + @Override + public int getPixelRGBA(TextureAtlasSprite sprite, int x, int y) { + try { + SpriteContents contents = sprite.contents(); + NativeImage image = contents.originalImage; + if (image != null) { + return image.getPixelRGBA(x, y); + } + return 0; + } catch (Exception e) { + DynamicTrees.LOG.warn("Failed to get pixel from sprite: {}", e.getMessage()); + return 0; + } + } + + @Override + public FallingTreeEntityModel newFallingTreeEntityModel(FallingTreeEntity entity) { + return new FallingTreeEntityModelFabric(entity); + } + +} diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricMiscHelper.java b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricMiscHelper.java index a3c2ea113..c62d584e1 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricMiscHelper.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricMiscHelper.java @@ -1,16 +1,10 @@ package com.dtteam.dynamictrees.platform; import com.dtteam.dynamictrees.DynamicTrees; -import com.dtteam.dynamictrees.entity.FallingTreeEntity; -import com.dtteam.dynamictrees.model.FallingTreeEntityModel; -import com.dtteam.dynamictrees.model.FallingTreeEntityModelFabric; import com.dtteam.dynamictrees.platform.services.IMiscHelper; import com.dtteam.dynamictrees.tree.species.Species; import com.dtteam.dynamictrees.worldgen.IDTBiomeHolderSet; import com.dtteam.dynamictrees.worldgen.holderset.DTBiomeHolderSet; -import com.mojang.blaze3d.platform.NativeImage; -import net.minecraft.client.renderer.texture.SpriteContents; -import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.server.MinecraftServer; import net.minecraft.world.level.Level; @@ -26,25 +20,6 @@ public static void debugSpeciesRegistry() { DynamicTrees.LOG.info("=== End Species Registry Debug ==="); } - @Override - public int getPixelRGBA(TextureAtlasSprite sprite, int x, int y) { - try { - SpriteContents contents = sprite.contents(); - NativeImage image = contents.originalImage; - if (image != null) { - return image.getPixelRGBA(x, y); - } - return 0; - } catch (Exception e) { - DynamicTrees.LOG.warn("Failed to get pixel from sprite: {}", e.getMessage()); - return 0; - } - } - - @Override - public FallingTreeEntityModel newFallingTreeEntityModel(FallingTreeEntity entity) { - return new FallingTreeEntityModelFabric(entity); - } @Override public boolean isLevelRestoringBlockSnapshots(Level level) { diff --git a/fabric/src/main/resources/META-INF/services/com.dtteam.dynamictrees.platform.services.IClientHelper b/fabric/src/main/resources/META-INF/services/com.dtteam.dynamictrees.platform.services.IClientHelper new file mode 100644 index 000000000..9c4dd1730 --- /dev/null +++ b/fabric/src/main/resources/META-INF/services/com.dtteam.dynamictrees.platform.services.IClientHelper @@ -0,0 +1 @@ +com.dtteam.dynamictrees.platform.FabricClientHelper diff --git a/neoforge/src/main/java/com/dtteam/dynamictrees/platform/NeoForgeClientHelper.java b/neoforge/src/main/java/com/dtteam/dynamictrees/platform/NeoForgeClientHelper.java new file mode 100644 index 000000000..9bc79d41e --- /dev/null +++ b/neoforge/src/main/java/com/dtteam/dynamictrees/platform/NeoForgeClientHelper.java @@ -0,0 +1,27 @@ +package com.dtteam.dynamictrees.platform; + +import com.dtteam.dynamictrees.DynamicTrees; +import com.dtteam.dynamictrees.entity.FallingTreeEntity; +import com.dtteam.dynamictrees.model.FallingTreeEntityModel; +import com.dtteam.dynamictrees.platform.services.IClientHelper; +import com.dtteam.dynamictrees.registry.FallingTreeEntityModelNF; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; + +public class NeoForgeClientHelper implements IClientHelper { + + @Override + public int getPixelRGBA(TextureAtlasSprite sprite, int x, int y) { + try { + return sprite.getPixelRGBA(0, x, y); + } catch (IllegalStateException e) { + DynamicTrees.LOG.warn("Image {} is not allocated.", sprite); + return 0; + } + } + + @Override + public FallingTreeEntityModel newFallingTreeEntityModel(FallingTreeEntity entity) { + return new FallingTreeEntityModelNF(entity); + } + +} diff --git a/neoforge/src/main/java/com/dtteam/dynamictrees/platform/NeoForgeMiscHelper.java b/neoforge/src/main/java/com/dtteam/dynamictrees/platform/NeoForgeMiscHelper.java index 18f038b80..d013b5375 100644 --- a/neoforge/src/main/java/com/dtteam/dynamictrees/platform/NeoForgeMiscHelper.java +++ b/neoforge/src/main/java/com/dtteam/dynamictrees/platform/NeoForgeMiscHelper.java @@ -1,33 +1,14 @@ package com.dtteam.dynamictrees.platform; -import com.dtteam.dynamictrees.DynamicTrees; -import com.dtteam.dynamictrees.entity.FallingTreeEntity; -import com.dtteam.dynamictrees.model.FallingTreeEntityModel; import com.dtteam.dynamictrees.platform.services.IMiscHelper; -import com.dtteam.dynamictrees.registry.FallingTreeEntityModelNF; import com.dtteam.dynamictrees.worldgen.IDTBiomeHolderSet; import com.dtteam.dynamictrees.worldgen.holderset.DTBiomeHolderSet; -import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.server.MinecraftServer; import net.minecraft.world.level.Level; import net.neoforged.neoforge.server.ServerLifecycleHooks; public class NeoForgeMiscHelper implements IMiscHelper { - @Override - public int getPixelRGBA(TextureAtlasSprite sprite, int x, int y) { - try{ - return sprite.getPixelRGBA(0, x, y); - } catch (IllegalStateException e){ - DynamicTrees.LOG.warn("Image {} is not allocated.", sprite); - return 0; - } - } - - @Override - public FallingTreeEntityModel newFallingTreeEntityModel(FallingTreeEntity entity) { - return new FallingTreeEntityModelNF(entity); - } @Override public boolean isLevelRestoringBlockSnapshots(Level level) { diff --git a/neoforge/src/main/resources/META-INF/services/com.dtteam.dynamictrees.platform.services.IClientHelper b/neoforge/src/main/resources/META-INF/services/com.dtteam.dynamictrees.platform.services.IClientHelper new file mode 100644 index 000000000..e5d2a8ee0 --- /dev/null +++ b/neoforge/src/main/resources/META-INF/services/com.dtteam.dynamictrees.platform.services.IClientHelper @@ -0,0 +1 @@ +com.dtteam.dynamictrees.platform.NeoForgeClientHelper From 206c4b4286c67a5aab20c2c1062555af14daf86f Mon Sep 17 00:00:00 2001 From: Nyfaria DragonMoose Date: Sat, 24 Jan 2026 13:34:47 -0600 Subject: [PATCH 5/9] fixed missing bottom rings texture and z fighting. --- .../model/FallingTreeEntityModelFabric.java | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/model/FallingTreeEntityModelFabric.java b/fabric/src/main/java/com/dtteam/dynamictrees/model/FallingTreeEntityModelFabric.java index ee05b303c..17204afd7 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/model/FallingTreeEntityModelFabric.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/model/FallingTreeEntityModelFabric.java @@ -69,7 +69,7 @@ public List generateTreeQuads(FallingTreeEntity entity) { float offset = (8 - Math.min(((BranchBlock) exState.getBlock()).getRadius(exState), BranchBlock.MAX_RADIUS)) / 16f; int coreRadius = ((BranchBlock) exState.getBlock()).getRadius(exState); treeQuads.addAll(toTreeQuadData( - getBranchQuadsWithConnections(branchModel, exState, new Vec3(offsetPos.getX(), offsetPos.getY(), offsetPos.getZ()).scale(offset), random, connectionArray, coreRadius, cutDir), + getBottomRingQuads(branchModel, new Vec3(offsetPos.getX(), offsetPos.getY(), offsetPos.getZ()).scale(offset), coreRadius, cutDir), exState)); bottomRingsAdded = true; } @@ -103,6 +103,29 @@ public List generateTreeQuads(FallingTreeEntity entity) { return treeQuads; } + private List getBottomRingQuads(BakedModel model, Vec3 offset, int coreRadius, Direction cutDir) { + List allQuads = new ArrayList<>(); + + if (model instanceof BasicBranchBlockBakedModel branchModel) { + List ringQuads = branchModel.ringsQuads[coreRadius - 1]; + for (BakedQuad quad : ringQuads) { + if (quad.getDirection() == cutDir) { + allQuads.add(quad); + } + } + } + + if (offset.x() != 0 || offset.y() != 0 || offset.z() != 0) { + List offsetQuads = new ArrayList<>(); + for (BakedQuad quad : allQuads) { + offsetQuads.add(offsetQuad(quad, offset)); + } + return offsetQuads; + } + + return allQuads; + } + private List getBranchQuadsWithConnections(BakedModel model, BlockState state, Vec3 offset, RandomSource random, int[] connections, int coreRadius, Direction forceRingDir) { List allQuads = new ArrayList<>(); From 029d947ac119f0f3b38548dd7dce1a201b4f7dfd Mon Sep 17 00:00:00 2001 From: Nyfaria DragonMoose Date: Sat, 24 Jan 2026 13:51:56 -0600 Subject: [PATCH 6/9] implemented the updated seasonal --- .../com/dtteam/dynamictrees/DynamicTreesFabricClient.java | 7 +++++-- .../dynamictrees/event/handler/CommonEventHandler.java | 3 ++- .../dtteam/dynamictrees/platform/FabricEventHelper.java | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricClient.java b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricClient.java index 0a855e83b..51152a0bc 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricClient.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/DynamicTreesFabricClient.java @@ -1,5 +1,6 @@ package com.dtteam.dynamictrees; +import com.dtteam.dynamictrees.api.season.*; import com.dtteam.dynamictrees.api.worldgen.*; import com.dtteam.dynamictrees.block.branch.*; import com.dtteam.dynamictrees.block.leaves.*; @@ -142,8 +143,10 @@ private void registerTooltipCallback() { return; } - int flags = seed.getSpecies().getSeasonalTooltipFlags(levelContext); - Tooltips.applySeasonalTooltips(lines, flags); + BlockPos playerPos = BlockPos.containing(player.position()); + ClimateZoneType climate = ClimateHelper.getClimate(player.level(), playerPos); + int flags = seed.getSpecies().getSeasonalTooltipFlags(levelContext, player); + Tooltips.applySeasonalTooltips(lines, flags, climate); }); } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/CommonEventHandler.java b/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/CommonEventHandler.java index c64b73681..f23816fe2 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/CommonEventHandler.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/CommonEventHandler.java @@ -7,6 +7,7 @@ import com.dtteam.dynamictrees.block.soil.SoilBlock; import com.dtteam.dynamictrees.command.DTCommand; import com.dtteam.dynamictrees.systems.FutureBreak; +import com.dtteam.dynamictrees.systems.season.SeasonCompatibilityHandler; import com.dtteam.dynamictrees.systems.season.SeasonHelper; import com.dtteam.dynamictrees.treepack.Resources; import com.dtteam.dynamictrees.worldgen.BiomeDatabases; @@ -50,7 +51,7 @@ public static void RegisterEvents(){ }); ServerLifecycleEvents.SERVER_STARTED.register((minecraftServer -> { - SeasonHelper.getSeasonManager().flushMappings(); + SeasonCompatibilityHandler.getSeasonManager().flushMappings(); })); CommandRegistrationCallback.EVENT.register(((commandDispatcher, commandBuildContext, commandSelection) -> { diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricEventHelper.java b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricEventHelper.java index a6f34fb78..762e9fde7 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricEventHelper.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricEventHelper.java @@ -73,7 +73,7 @@ public void cropGrowPost(Level level, BlockPos pos, BlockState state) { @Override public Species.BiomeSuitabilityEventResult postBiomeSuitabilityEvent(Level level, Biome biome, Species species, BlockPos pos) { - return new Species.BiomeSuitabilityEventResult(false, Species.defaultSuitability()); + return new Species.BiomeSuitabilityEventResult(false, 0.0f); } @Override From f3162855d715ee3d8e46d5e1905165ef4e6aed41 Mon Sep 17 00:00:00 2001 From: Nyfaria DragonMoose Date: Sat, 24 Jan 2026 14:06:57 -0600 Subject: [PATCH 7/9] remove debug mixin --- .../mixin/MixinDynamicTreeFeature.java | 85 ------------------- .../resources/dynamictrees.fabric.mixins.json | 1 - 2 files changed, 86 deletions(-) delete mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinDynamicTreeFeature.java diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinDynamicTreeFeature.java b/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinDynamicTreeFeature.java deleted file mode 100644 index e591be35a..000000000 --- a/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinDynamicTreeFeature.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.dtteam.dynamictrees.mixin; - -import com.dtteam.dynamictrees.DynamicTrees; -import com.dtteam.dynamictrees.api.worldgen.BiomePropertySelectors; -import com.dtteam.dynamictrees.api.worldgen.RandomXOR; -import com.dtteam.dynamictrees.tree.species.Species; -import com.dtteam.dynamictrees.worldgen.BiomeDatabase; -import com.dtteam.dynamictrees.worldgen.feature.DynamicTreeFeature; -import net.minecraft.core.BlockPos; -import net.minecraft.world.level.block.Blocks; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -import java.lang.reflect.Field; -import java.util.List; - -@Mixin(value = DynamicTreeFeature.class, remap = false) -public abstract class MixinDynamicTreeFeature { - - @Unique - private static boolean dynamictrees$loggedCherrySelector = false; - - @Unique - private static final RandomXOR dynamictrees$debugRandom = new RandomXOR(); - - @Inject(method = "getSpeciesSelector", at = @At("RETURN"), remap = false) - private void onGetSpeciesSelector(BiomeDatabase.EntryReader biomeEntry, CallbackInfoReturnable cir) { - if (dynamictrees$loggedCherrySelector) { - return; - } - if (biomeEntry != null && biomeEntry.getBiomeKey() != null) { - String biomeName = biomeEntry.getBiomeKey().location().toString(); - if (biomeName.contains("cherry")) { - dynamictrees$loggedCherrySelector = true; - BiomePropertySelectors.SpeciesSelector selector = cir.getReturnValue(); - DynamicTrees.LOG.info("Cherry biome species selector type: {} for biome {}", - selector.getClass().getSimpleName(), biomeName); - - dynamictrees$debugRandom.setXOR(BlockPos.ZERO); - try { - BiomePropertySelectors.SpeciesSelection selection = selector.getSpecies( - BlockPos.ZERO, Blocks.DIRT.defaultBlockState(), dynamictrees$debugRandom); - Species species = selection.getSpecies(); - DynamicTrees.LOG.info("Cherry biome would select species: {} (valid: {}, handled: {})", - species.getRegistryName(), species.isValid(), selection.isHandled()); - } catch (Exception e) { - DynamicTrees.LOG.error("Failed to test species selection for cherry biome", e); - } - - if (selector instanceof BiomePropertySelectors.RandomSpeciesSelector randomSelector) { - dynamictrees$logRandomSelectorContents(randomSelector); - } - } - } - } - - @Unique - private void dynamictrees$logRandomSelectorContents(BiomePropertySelectors.RandomSpeciesSelector selector) { - try { - Field decisionTableField = BiomePropertySelectors.RandomSpeciesSelector.class.getDeclaredField("decisionTable"); - decisionTableField.setAccessible(true); - List decisionTable = (List) decisionTableField.get(selector); - - DynamicTrees.LOG.info("RandomSpeciesSelector contains {} entries:", decisionTable.size()); - - for (Object entry : decisionTable) { - Field decisionField = entry.getClass().getDeclaredField("decision"); - Field weightField = entry.getClass().getDeclaredField("weight"); - decisionField.setAccessible(true); - weightField.setAccessible(true); - - BiomePropertySelectors.SpeciesSelection decision = (BiomePropertySelectors.SpeciesSelection) decisionField.get(entry); - int weight = (int) weightField.get(entry); - - DynamicTrees.LOG.info(" - Species: {}, Weight: {}, Handled: {}", - decision.getSpecies().getRegistryName(), weight, decision.isHandled()); - } - } catch (Exception e) { - DynamicTrees.LOG.error("Failed to log RandomSpeciesSelector contents", e); - } - } -} diff --git a/fabric/src/main/resources/dynamictrees.fabric.mixins.json b/fabric/src/main/resources/dynamictrees.fabric.mixins.json index 18e11a145..71eeb9a81 100644 --- a/fabric/src/main/resources/dynamictrees.fabric.mixins.json +++ b/fabric/src/main/resources/dynamictrees.fabric.mixins.json @@ -9,7 +9,6 @@ "MixinBrewingStandMenu", "MixinBushBlock", "MixinChunkSerializer", - "MixinDynamicTreeFeature", "MixinMinecraftServer", "MixinPotionBrewing", "MixinSaplingBlock" From 667812d43d599edd8fbce20fdde544ce5df0578f Mon Sep 17 00:00:00 2001 From: Nyfaria DragonMoose Date: Sat, 24 Jan 2026 15:32:06 -0600 Subject: [PATCH 8/9] minor fixes, AWs in mod json --- .../java/com/dtteam/dynamictrees/config/DTConfigs.java | 7 +++++-- fabric/src/main/resources/fabric.mod.json | 1 + gradle.properties | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/config/DTConfigs.java b/fabric/src/main/java/com/dtteam/dynamictrees/config/DTConfigs.java index 881ab9cd6..7f7057936 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/config/DTConfigs.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/config/DTConfigs.java @@ -28,6 +28,8 @@ private static void createConfigs() { IConfigHelper.MIN_SEASONAL_LEAVES_SEED_DROP_RATE, 0.15, 0.0, 1.0); createConfig("The rate at which seeds voluntarily drop from branches", IConfigHelper.VOLUNTARY_SEED_DROP_RATE, 0.01, 0.0, 1.0); + createConfig("The minimum chance for seed dropping voluntarily when a seasonal mod is installed. 0 = during the off season seeds never drop voluntarily, 1 = seeds will drop at maximum rate during the entire year. Can be fractional.", + IConfigHelper.MIN_SEASONAL_VOLUNTARY_SEED_DROP_RATE, 0.0, 0.0, 1.0); createConfig("The rate at which seeds voluntarily plant themselves in their ideal biomes", IConfigHelper.SEED_PLANT_RATE, 1f / 6f, 0.0, 1.0); createConfig("Ticks before a seed in the world attempts to plant itself or despawn. 1200 = 1 minute", @@ -36,6 +38,8 @@ private static void createConfigs() { IConfigHelper.SEED_ONLY_FOREST, true); createConfig("The minimum forestness that non-forest-like biomes can have. 0 = is not at all a forest, 1 = may as well be a forest. Can be fractional.", IConfigHelper.SEED_MIN_FORESTNESS, 0.0, 0.0, 1.0); + createConfig("If enabled, fruit and pod production will be affected by the current biome's climate.", + IConfigHelper.CLIMATE_AFFECTS_FRUITS_AND_PODS, true); configs.addSection("trees"); createConfig("Factor that multiplies the rate at which trees grow. Use at own risk", @@ -45,8 +49,7 @@ private static void createConfigs() { createConfig("Maximum harvesting hardness that can be calculated. Regardless of tree thickness.", IConfigHelper.MAX_TREE_HARDNESS, 20f, 1f, 200f); createConfig("A multiplier of tree hardness. Higher values make trees slower to chop, lower values makes them faster to chop.", - - IConfigHelper.TREE_HARDNESS_MULTIPLIER, 1, (1/128f), 32f); + IConfigHelper.TREE_HARDNESS_MULTIPLIER, 1f, (1/128f), 32f); createConfig("If enabled then sticks will be dropped for partial logs", IConfigHelper.DROP_STICKS, true); createConfig("Scales the growth for the environment. 0.5f is nominal. 0.0 trees only grow in their native biome. 1.0 trees grow anywhere like they are in their native biome", diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index 654ae79b8..f43870697 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -14,6 +14,7 @@ "license": "${license}", "icon": "${mod_id}.png", "environment": "*", + "accessWidener": "META-INF/${mod_id}.accesswidener", "entrypoints": { "main": [ "com.dtteam.dynamictrees.DynamicTreesFabric" diff --git a/gradle.properties b/gradle.properties index bae7c11a8..31ad06d3d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -26,7 +26,7 @@ java_version=21 # Fabric fabric_version=0.109.0+1.21.1 -fabric_loader_version=0.16.9 +fabric_loader_version=0.18.4 # Forge // Unused forge_version=52.0.28 From 5b93988925b826ba93e94cbf28f858f930c7b867 Mon Sep 17 00:00:00 2001 From: Nyfaria DragonMoose Date: Sun, 25 Jan 2026 16:57:13 -0600 Subject: [PATCH 9/9] config rework. --- build.gradle | 4 +- .../src/main/groovy/multiloader-common.gradle | 5 + common/build.gradle | 3 + .../block/branch/BasicBranchBlock.java | 9 +- .../block/branch/BasicRootsBlock.java | 7 +- .../block/branch/BranchBlock.java | 6 +- .../dynamictrees/block/fruit/Fruit.java | 5 +- .../block/leaves/DynamicLeavesBlock.java | 11 +- .../dtteam/dynamictrees/block/pod/Pod.java | 5 +- .../block/sapling/DynamicSaplingBlock.java | 6 +- .../block/soil/AerialRootsSoilProperties.java | 7 +- .../dynamictrees/block/soil/SoilBlock.java | 7 +- .../block/soil/WaterSoilProperties.java | 5 +- .../dtteam/dynamictrees/config/DTConfigs.java | 187 +++++++++++++ .../data/DirtBucketRecipeHandler.java | 7 +- .../entity/FallingTreeEntity.java | 6 +- .../animation/FalloverAnimationHandler.java | 11 +- .../dtteam/dynamictrees/item/DirtBucket.java | 5 +- .../com/dtteam/dynamictrees/item/Seed.java | 10 +- .../condition/SeasonalSeedDropChance.java | 8 +- .../condition/VoluntarySeedDropChance.java | 7 +- .../systems/genfeature/PodzolGenFeature.java | 5 +- .../season/ActiveSeasonGrowthCalculator.java | 5 +- .../season/SeasonCompatibilityHandler.java | 4 +- .../systems/season/SeasonHelper.java | 11 +- .../dynamictrees/tree/family/Family.java | 6 +- .../dynamictrees/tree/species/Species.java | 24 +- .../tree/species/SwampSpecies.java | 5 +- .../dynamictrees/treepack/Resources.java | 6 +- .../dynamictrees/utility/ItemUtils.java | 4 +- .../dynamictrees/worldgen/BiomeDatabase.java | 5 +- .../dynamictrees/worldgen/BiomeDatabases.java | 7 +- ...TReplaceNyliumFungiBlockStateProvider.java | 5 +- .../worldgen/feature/DynamicTreeFeature.java | 5 +- .../DTCancelVanillaTreePoolElement.java | 5 +- fabric/build.gradle | 4 +- fabric/libs/core-3.6.7.jar | Bin 207057 -> 0 bytes fabric/libs/toml-3.6.7.jar | Bin 34119 -> 0 bytes .../dynamictrees/DynamicTreesFabric.java | 7 +- .../DynamicTreesFabricClient.java | 4 + .../dynamictrees/config/DTConfigProvider.java | 36 --- .../dtteam/dynamictrees/config/DTConfigs.java | 159 ----------- .../dynamictrees/config/SimpleConfig.java | 260 ------------------ .../handler/VanillaSaplingEventHandler.java | 5 +- .../mixin/MixinChunkSerializer.java | 7 +- .../dynamictrees/mixin/MixinSaplingBlock.java | 5 +- .../platform/FabricConfigHelper.java | 59 +--- .../recipe/DendroPotionRecipeHandler.java | 7 +- .../worldgen/FabricBiomeModifications.java | 15 +- gradle.properties | 4 +- gradle/wrapper/gradle-wrapper.properties | 2 +- .../dynamictrees/DynamicTreesNeoForge.java | 2 +- .../dynamictrees/config/DTConfigEvents.java | 25 ++ .../dtteam/dynamictrees/config/DTConfigs.java | 192 ------------- .../event/handler/CommonGameEventHandler.java | 7 +- .../event/handler/OptionalHandlers.java | 5 +- .../platform/NeoForgeConfigHelper.java | 2 +- .../recipe/DendroPotionRecipeHandler.java | 7 +- .../AddDynamicTreesBiomeModifier.java | 5 +- .../RunFeatureCancellersBiomeModifier.java | 5 +- settings.gradle | 1 + 61 files changed, 377 insertions(+), 866 deletions(-) create mode 100644 common/src/main/java/com/dtteam/dynamictrees/config/DTConfigs.java delete mode 100644 fabric/libs/core-3.6.7.jar delete mode 100644 fabric/libs/toml-3.6.7.jar delete mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/config/DTConfigProvider.java delete mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/config/DTConfigs.java delete mode 100644 fabric/src/main/java/com/dtteam/dynamictrees/config/SimpleConfig.java create mode 100644 neoforge/src/main/java/com/dtteam/dynamictrees/config/DTConfigEvents.java delete mode 100644 neoforge/src/main/java/com/dtteam/dynamictrees/config/DTConfigs.java diff --git a/build.gradle b/build.gradle index 395289a89..9b753970a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ plugins { // see https://fabricmc.net/develop/ for new versions - id 'fabric-loom' version '1.9-SNAPSHOT' apply false + id 'fabric-loom' version '1.13-SNAPSHOT' apply false // see https://projects.neoforged.net/neoforged/moddevgradle for new versions - id 'net.neoforged.moddev' version '2.0.49-beta' apply false + id 'net.neoforged.moddev' version '2.0.140' apply false } diff --git a/buildSrc/src/main/groovy/multiloader-common.gradle b/buildSrc/src/main/groovy/multiloader-common.gradle index e2b0047fa..ad5ee49c8 100644 --- a/buildSrc/src/main/groovy/multiloader-common.gradle +++ b/buildSrc/src/main/groovy/multiloader-common.gradle @@ -42,6 +42,11 @@ repositories { name = 'BlameJared' url = 'https://maven.blamejared.com' } + + maven { + name = "Fuzs Mod Resources" + url = "https://raw.githubusercontent.com/Fuzss/modresources/main/maven/" + } } // Declare capabilities on the outgoing configurations. diff --git a/common/build.gradle b/common/build.gradle index ea7000a89..f96e37166 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -19,11 +19,14 @@ neoForge { } } + + dependencies { compileOnly group: 'org.spongepowered', name: 'mixin', version: '0.8.5' // fabric and neoforge both bundle mixinextras, so it is safe to use it in common compileOnly group: 'io.github.llamalad7', name: 'mixinextras-common', version: '0.3.5' annotationProcessor group: 'io.github.llamalad7', name: 'mixinextras-common', version: '0.3.5' + compileOnly "fuzs.forgeconfigapiport:forgeconfigapiport-common-neoforgeapi:${project.forge_config_api_port_version}" } configurations { diff --git a/common/src/main/java/com/dtteam/dynamictrees/block/branch/BasicBranchBlock.java b/common/src/main/java/com/dtteam/dynamictrees/block/branch/BasicBranchBlock.java index 1dd31bf24..ca44f9f99 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/block/branch/BasicBranchBlock.java +++ b/common/src/main/java/com/dtteam/dynamictrees/block/branch/BasicBranchBlock.java @@ -8,8 +8,7 @@ import com.dtteam.dynamictrees.block.leaves.DynamicLeavesBlock; import com.dtteam.dynamictrees.block.leaves.LeavesProperties; import com.dtteam.dynamictrees.block.pod.OffsetablePodBlock; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.systems.GrowSignal; import com.dtteam.dynamictrees.systems.cell.MetadataCell; import com.dtteam.dynamictrees.systems.growthlogic.context.DirectionSelectionContext; @@ -192,8 +191,8 @@ public boolean canPlaceLiquid(@Nullable Player player, BlockGetter level, BlockP public float getHardness(BlockState state, BlockGetter level, BlockPos pos) { final int radius = this.getRadius(level.getBlockState(pos)); final double hardness = this.getFamily().getPrimitiveLog().orElse(Blocks.AIR).defaultBlockState() - .getDestroySpeed(level, pos) * Services.CONFIG.getDoubleConfig(IConfigHelper.TREE_HARDNESS_MULTIPLIER) * (radius * radius) / 64.0f * 8.0f; - return (float) Math.min(hardness, Services.CONFIG.getDoubleConfig(IConfigHelper.MAX_TREE_HARDNESS)); // So many youtube let's plays start with "OMG, this is taking so long to break this tree!" + .getDestroySpeed(level, pos) * DTConfigs.SERVER.treeHardnessMultiplier.get() * (radius * radius) / 64.0f * 8.0f; + return (float) Math.min(hardness, DTConfigs.SERVER.maxTreeHardness.get()); } /** NeoForge override */ @@ -370,7 +369,7 @@ public GrowSignal growSignal(Level level, BlockPos pos, GrowSignal signal) { /** NeoForge Override */ @SuppressWarnings("unused") public boolean isLadder(BlockState state, LevelReader level, BlockPos pos, LivingEntity entity) { - return Services.CONFIG.getBoolConfig(IConfigHelper.ENABLE_BRANCH_CLIMBING) && + return DTConfigs.SERVER.enableBranchClimbing.get() && entity instanceof Player && getFamily().branchIsLadder() && (!state.hasProperty(WATERLOGGED) || !state.getValue(WATERLOGGED)); diff --git a/common/src/main/java/com/dtteam/dynamictrees/block/branch/BasicRootsBlock.java b/common/src/main/java/com/dtteam/dynamictrees/block/branch/BasicRootsBlock.java index c6a90f54b..f3aaf7a55 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/block/branch/BasicRootsBlock.java +++ b/common/src/main/java/com/dtteam/dynamictrees/block/branch/BasicRootsBlock.java @@ -11,9 +11,8 @@ import com.dtteam.dynamictrees.block.soil.AerialRootsSoilProperties; import com.dtteam.dynamictrees.block.soil.SoilBlock; import com.dtteam.dynamictrees.entity.FallingTreeEntity; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.loot.LootTableSupplier; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; import com.dtteam.dynamictrees.systems.GrowSignal; import com.dtteam.dynamictrees.systems.growthlogic.context.DirectionSelectionContext; import com.dtteam.dynamictrees.systems.nodemapper.NetVolumeNode; @@ -411,8 +410,8 @@ public float getHardness(BlockState state, BlockGetter level, BlockPos pos) { if (isFullBlock(state)) return getFamily().getPrimitiveCoveredRoots().orElse(Blocks.AIR).defaultDestroyTime(); final int radius = this.getRadius(level.getBlockState(pos)); final double hardness = this.getFamily().getPrimitiveLog().orElse(Blocks.AIR).defaultBlockState() - .getDestroySpeed(level, pos) * Services.CONFIG.getDoubleConfig(IConfigHelper.TREE_HARDNESS_MULTIPLIER) * (radius * radius) / 64.0f * 8.0f; - return (float) Math.min(hardness, Services.CONFIG.getDoubleConfig(IConfigHelper.MAX_TREE_HARDNESS)); // So many youtube let's plays start with "OMG, this is taking so long to break this tree!" + .getDestroySpeed(level, pos) * DTConfigs.SERVER.treeHardnessMultiplier.get() * (radius * radius) / 64.0f * 8.0f; + return (float) Math.min(hardness, DTConfigs.SERVER.maxTreeHardness.get()); } //This is the state that will replace the root when the tree is felled. diff --git a/common/src/main/java/com/dtteam/dynamictrees/block/branch/BranchBlock.java b/common/src/main/java/com/dtteam/dynamictrees/block/branch/BranchBlock.java index 0dee6fb8d..fe953ab91 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/block/branch/BranchBlock.java +++ b/common/src/main/java/com/dtteam/dynamictrees/block/branch/BranchBlock.java @@ -15,9 +15,9 @@ import com.dtteam.dynamictrees.block.soil.SoilBlock; import com.dtteam.dynamictrees.data.DTLootTableBuilder; import com.dtteam.dynamictrees.entity.FallingTreeEntity; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.loot.LootTableSupplier; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; +import com.dtteam.dynamictrees.platform.*; import com.dtteam.dynamictrees.systems.FutureBreak; import com.dtteam.dynamictrees.systems.nodemapper.DestroyerNode; import com.dtteam.dynamictrees.systems.nodemapper.NetVolumeNode; @@ -631,7 +631,7 @@ protected void sloppyBreak(Level level, BlockPos cutPos, FallingTreeEntity.Destr final List woodDropList = destroyData.species.getBranchesDrops(level, destroyData.woodVolume); // If sloppy break drops are off clear all drops. - if (!Services.CONFIG.getBoolConfig(IConfigHelper.SLOPPY_BREAK_DROPS)) { + if (!DTConfigs.SERVER.sloppyBreakDrops.get()) { destroyData.leavesDrops.clear(); woodDropList.clear(); } diff --git a/common/src/main/java/com/dtteam/dynamictrees/block/fruit/Fruit.java b/common/src/main/java/com/dtteam/dynamictrees/block/fruit/Fruit.java index 5f701d79e..805d2bcfa 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/block/fruit/Fruit.java +++ b/common/src/main/java/com/dtteam/dynamictrees/block/fruit/Fruit.java @@ -10,9 +10,8 @@ import com.dtteam.dynamictrees.api.worldgen.LevelContext; import com.dtteam.dynamictrees.block.DynamicBlockProperties; import com.dtteam.dynamictrees.block.Growable; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.data.DTLootTableBuilder; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; import com.dtteam.dynamictrees.systems.season.SeasonHelper; import com.dtteam.dynamictrees.treepack.Resettable; import com.dtteam.dynamictrees.utility.ResourceLocationUtils; @@ -304,7 +303,7 @@ public LootTable.Builder createBlockDrops(HolderLookup.Provider registries) { @NotNull @Override public Fruit reset() { - canBoneMeal = Services.CONFIG.isServerConfigLoaded() && Services.CONFIG.getBoolConfig(IConfigHelper.CAN_BONE_MEAL_FRUIT); + canBoneMeal = DTConfigs.SERVER_CONFIG.isLoaded() && DTConfigs.SERVER.canBoneMealFruit.get(); requiredProductionFactor = 0.3F; matureAction = Growable.MatureAction.DEFAULT; seasonalFactorGetter = (l,b)-> 1.0f; diff --git a/common/src/main/java/com/dtteam/dynamictrees/block/leaves/DynamicLeavesBlock.java b/common/src/main/java/com/dtteam/dynamictrees/block/leaves/DynamicLeavesBlock.java index 785fe02d4..b2bc880b8 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/block/leaves/DynamicLeavesBlock.java +++ b/common/src/main/java/com/dtteam/dynamictrees/block/leaves/DynamicLeavesBlock.java @@ -12,8 +12,8 @@ import com.dtteam.dynamictrees.data.tags.DTEntityTypeTags; import com.dtteam.dynamictrees.item.Seed; import com.dtteam.dynamictrees.loot.DTLootContextParams; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; import com.dtteam.dynamictrees.systems.GrowSignal; import com.dtteam.dynamictrees.tree.ChunkTreeHelper; import com.dtteam.dynamictrees.tree.TreeHelper; @@ -123,7 +123,7 @@ public int age(LevelAccessor level, BlockPos pos, BlockState state, RandomSource */ @Override public void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource rand) { - double growthMultiplier = Services.CONFIG.getDoubleConfig(IConfigHelper.TREE_GROWTH_MULTIPLIER); + double growthMultiplier = DTConfigs.SERVER.treeGrowthMultiplier.get(); if (rand.nextFloat() > growthMultiplier) { return; } @@ -545,11 +545,11 @@ public VoxelShape getBlockSupportShape(BlockState pState, BlockGetter pReader, B } protected boolean isMovementVanilla(){ - return Services.CONFIG.isServerConfigLoaded() && Services.CONFIG.getBoolConfig(IConfigHelper.VANILLA_LEAVES_COLLISION); + return DTConfigs.SERVER_CONFIG.isLoaded() && DTConfigs.SERVER.vanillaLeavesCollision.get(); } protected boolean isLeavesPassable() { - return (Services.CONFIG.isServerConfigLoaded() && Services.CONFIG.getBoolConfig(IConfigHelper.IS_LEAVES_PASSABLE)) + return (DTConfigs.SERVER_CONFIG.isLoaded() && DTConfigs.SERVER.isLeavesPassable.get()) || Services.PLATFORM.isModLoaded(DynamicTrees.PASSABLE_FOLIAGE); } @@ -580,8 +580,7 @@ protected void superFallOn(Level level, BlockState blockState, BlockPos pos, Ent @Override public void fallOn(Level level, BlockState state, BlockPos pos, Entity entity, float fallDistance) { - // We are only interested in Living things crashing through the canopy. - if (!Services.CONFIG.getBoolConfig(IConfigHelper.ENABLE_CANOPY_CRASH) || !(entity instanceof LivingEntity)) { + if (!DTConfigs.SERVER.enableCanopyCrash.get() || !(entity instanceof LivingEntity)) { return; } diff --git a/common/src/main/java/com/dtteam/dynamictrees/block/pod/Pod.java b/common/src/main/java/com/dtteam/dynamictrees/block/pod/Pod.java index de818532b..c799938a6 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/block/pod/Pod.java +++ b/common/src/main/java/com/dtteam/dynamictrees/block/pod/Pod.java @@ -9,9 +9,8 @@ import com.dtteam.dynamictrees.api.worldgen.LevelContext; import com.dtteam.dynamictrees.block.DynamicBlockProperties; import com.dtteam.dynamictrees.block.Growable; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.data.DTLootTableBuilder; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; import com.dtteam.dynamictrees.treepack.Resettable; import com.dtteam.dynamictrees.utility.ResourceLocationUtils; import com.google.common.collect.Maps; @@ -362,7 +361,7 @@ public IntegerProperty getOffsetProperty (){ @NotNull @Override public Pod reset() { - canBoneMeal = Services.CONFIG.isServerConfigLoaded() && Services.CONFIG.getBoolConfig(IConfigHelper.CAN_BONE_MEAL_PODS); + canBoneMeal = DTConfigs.SERVER_CONFIG.isLoaded() && DTConfigs.SERVER.canBoneMealPods.get(); requiredProductionFactor = 0.3F; matureAction = Growable.MatureAction.DEFAULT; seasonalFactorGetter = (l,b)-> 1.0f; diff --git a/common/src/main/java/com/dtteam/dynamictrees/block/sapling/DynamicSaplingBlock.java b/common/src/main/java/com/dtteam/dynamictrees/block/sapling/DynamicSaplingBlock.java index ce28bfdb7..2237a06c9 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/block/sapling/DynamicSaplingBlock.java +++ b/common/src/main/java/com/dtteam/dynamictrees/block/sapling/DynamicSaplingBlock.java @@ -1,7 +1,6 @@ package com.dtteam.dynamictrees.block.sapling; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.tree.TreeHelper; import com.dtteam.dynamictrees.tree.species.Species; import com.dtteam.dynamictrees.utility.CoordUtils; @@ -145,8 +144,7 @@ public ItemStack getCloneItemStack(LevelReader level, BlockPos pos, BlockState s @Override public List getDrops(@NotNull BlockState state, @NotNull LootParams.Builder builder) { - // Drop nothing if sapling drops are disabled, nuthin'! - if (!Services.CONFIG.getBoolConfig(IConfigHelper.DYNAMIC_SAPLING_DROPS)) + if (!DTConfigs.SERVER.dynamicSaplingDrops.get()) return Collections.emptyList(); // If a loot table has been added load those drops instead. LootTable loottable = builder.getLevel().getServer().reloadableRegistries().getLootTable(getLootTable()); diff --git a/common/src/main/java/com/dtteam/dynamictrees/block/soil/AerialRootsSoilProperties.java b/common/src/main/java/com/dtteam/dynamictrees/block/soil/AerialRootsSoilProperties.java index c9db12187..081294da3 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/block/soil/AerialRootsSoilProperties.java +++ b/common/src/main/java/com/dtteam/dynamictrees/block/soil/AerialRootsSoilProperties.java @@ -7,11 +7,10 @@ import com.dtteam.dynamictrees.api.treedata.TreePart; import com.dtteam.dynamictrees.block.BlockWithDynamicHardness; import com.dtteam.dynamictrees.block.branch.BranchBlock; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.data.tags.DTBlockTags; import com.dtteam.dynamictrees.entity.FallingTreeEntity; -import com.dtteam.dynamictrees.entity.animation.FalloverAnimationHandler; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; +import com.dtteam.dynamictrees.entity.animation.*; import com.dtteam.dynamictrees.systems.nodemapper.NetVolumeNode; import com.dtteam.dynamictrees.systems.nodemapper.RootIntegrityNode; import com.dtteam.dynamictrees.tree.ChunkTreeHelper; @@ -142,7 +141,7 @@ public float getHardness(BlockState state, BlockGetter level, BlockPos pos) { if (up.getBlock() instanceof BlockWithDynamicHardness upBlock){ hardness = upBlock.getHardness(up, level, pos.above()); } - return (float)(hardness * Services.CONFIG.getDoubleConfig(IConfigHelper.ROOTY_BLOCK_HARDNESS_MULTIPLIER)); + return (float)(hardness * DTConfigs.SERVER.rootyBlockHardnessMultiplier.get()); } @Override diff --git a/common/src/main/java/com/dtteam/dynamictrees/block/soil/SoilBlock.java b/common/src/main/java/com/dtteam/dynamictrees/block/soil/SoilBlock.java index c152baf21..7342c40e8 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/block/soil/SoilBlock.java +++ b/common/src/main/java/com/dtteam/dynamictrees/block/soil/SoilBlock.java @@ -10,9 +10,8 @@ import com.dtteam.dynamictrees.block.branch.BasicRootsBlock; import com.dtteam.dynamictrees.block.branch.BranchBlock; import com.dtteam.dynamictrees.block.leaves.LeavesProperties; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.entity.FallingTreeEntity; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; import com.dtteam.dynamictrees.systems.GrowSignal; import com.dtteam.dynamictrees.tree.ChunkTreeHelper; import com.dtteam.dynamictrees.tree.TreeHelper; @@ -167,7 +166,7 @@ public ItemStack getCloneItemStack(LevelReader level, BlockPos pos, BlockState s @Override public float getHardness(BlockState state, BlockGetter level, BlockPos pos) { - return (float) (getPrimitiveSoilState(state).getDestroySpeed(level, pos) * Services.CONFIG.getDoubleConfig(IConfigHelper.ROOTY_BLOCK_HARDNESS_MULTIPLIER)); + return (float) (getPrimitiveSoilState(state).getDestroySpeed(level, pos) * DTConfigs.SERVER.rootyBlockHardnessMultiplier.get()); } /////////////////////////////////////////// @@ -207,7 +206,7 @@ public boolean hasTileEntity(BlockState state) { @Override public void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) { - double growthMultiplier = Services.CONFIG.getDoubleConfig(IConfigHelper.TREE_GROWTH_MULTIPLIER); + double growthMultiplier = DTConfigs.SERVER.treeGrowthMultiplier.get(); //Growth multiplier lower than 1 causes only some ticks to grow if (random.nextFloat() > growthMultiplier) return; diff --git a/common/src/main/java/com/dtteam/dynamictrees/block/soil/WaterSoilProperties.java b/common/src/main/java/com/dtteam/dynamictrees/block/soil/WaterSoilProperties.java index b49280c7e..7330153de 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/block/soil/WaterSoilProperties.java +++ b/common/src/main/java/com/dtteam/dynamictrees/block/soil/WaterSoilProperties.java @@ -3,8 +3,7 @@ import com.dtteam.dynamictrees.DynamicTrees; import com.dtteam.dynamictrees.api.registry.TypedRegistry; import com.dtteam.dynamictrees.block.branch.BranchBlock; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.tree.TreeHelper; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -87,7 +86,7 @@ public ItemStack getCloneItemStack(LevelReader level, BlockPos pos, BlockState s @Override public float getHardness(BlockState state, BlockGetter level, BlockPos pos) { - return (float) (0.5 * Services.CONFIG.getDoubleConfig(IConfigHelper.ROOTY_BLOCK_HARDNESS_MULTIPLIER)); + return (float) (0.5 * DTConfigs.SERVER.rootyBlockHardnessMultiplier.get()); } @Override diff --git a/common/src/main/java/com/dtteam/dynamictrees/config/DTConfigs.java b/common/src/main/java/com/dtteam/dynamictrees/config/DTConfigs.java new file mode 100644 index 000000000..35990e5eb --- /dev/null +++ b/common/src/main/java/com/dtteam/dynamictrees/config/DTConfigs.java @@ -0,0 +1,187 @@ +package com.dtteam.dynamictrees.config; + +import com.dtteam.dynamictrees.*; +import com.dtteam.dynamictrees.block.branch.*; +import com.dtteam.dynamictrees.systems.season.*; +import com.dtteam.dynamictrees.tree.species.*; +import net.neoforged.neoforge.common.*; +import org.apache.commons.lang3.tuple.*; + +import java.util.*; + +public class DTConfigs { + + public static final ModConfigSpec SERVER_CONFIG; + public static final DTConfigs SERVER; + public static final ModConfigSpec COMMON_CONFIG; + public static final DTConfigs COMMON; + public static final ModConfigSpec CLIENT_CONFIG; + public static final DTConfigs CLIENT; + + static { + Pair serverPair = new ModConfigSpec.Builder().configure(DTConfigs::buildServerConfig); + SERVER_CONFIG = serverPair.getRight(); + SERVER = serverPair.getLeft(); + + Pair commonPair = new ModConfigSpec.Builder().configure(DTConfigs::buildCommonConfig); + COMMON_CONFIG = commonPair.getRight(); + COMMON = commonPair.getLeft(); + + Pair clientPair = new ModConfigSpec.Builder().configure(DTConfigs::buildClientConfig); + CLIENT_CONFIG = clientPair.getRight(); + CLIENT = clientPair.getLeft(); + } + + public ModConfigSpec.DoubleValue leavesSeedDropRate; + public ModConfigSpec.DoubleValue minSeasonalLeavesSeedDropRate; + public ModConfigSpec.DoubleValue voluntarySeedDropRate; + public ModConfigSpec.DoubleValue minSeasonalVoluntarySeedDropRate; + public ModConfigSpec.DoubleValue seedPlantRate; + public ModConfigSpec.IntValue seedTimeToLive; + public ModConfigSpec.BooleanValue seedOnlyForest; + public ModConfigSpec.DoubleValue seedMinForestness; + public ModConfigSpec.BooleanValue climateAffectsFruitsAndPods; + + public ModConfigSpec.DoubleValue treeGrowthMultiplier; + public ModConfigSpec.DoubleValue treeHarvestMultiplier; + public ModConfigSpec.DoubleValue maxTreeHardness; + public ModConfigSpec.DoubleValue treeHardnessMultiplier; + public ModConfigSpec.BooleanValue dropSticks; + public ModConfigSpec.DoubleValue scaleBiomeGrowthRate; + public ModConfigSpec.DoubleValue diseaseChance; + public ModConfigSpec.IntValue maxBranchRotRadius; + public ModConfigSpec.DoubleValue rootyBlockHardnessMultiplier; + public ModConfigSpec.EnumValue swampOaksInWater; + public ModConfigSpec.IntValue boneMealGrowthPulses; + + public ModConfigSpec.BooleanValue isLeavesPassable; + public ModConfigSpec.BooleanValue vanillaLeavesCollision; + public ModConfigSpec.BooleanValue enableBranchClimbing; + public ModConfigSpec.BooleanValue enableCanopyCrash; + public ModConfigSpec.EnumValue axeDamageMode; + public ModConfigSpec.BooleanValue enableFallingTrees; + public ModConfigSpec.BooleanValue enableFallingTreeDamage; + public ModConfigSpec.DoubleValue fallingTreeDamageMultiplier; + public ModConfigSpec.BooleanValue dirtBucketPlacesDirt; + public ModConfigSpec.BooleanValue sloppyBreakDrops; + public ModConfigSpec.IntValue minRadiusForStrip; + public ModConfigSpec.BooleanValue enableStripRadiusReduction; + public ModConfigSpec.BooleanValue canBoneMealFruit; + public ModConfigSpec.BooleanValue canBoneMealPods; + public ModConfigSpec.BooleanValue dynamicSaplingDrops; + + public ModConfigSpec.BooleanValue replaceVanillaSaplings; + public ModConfigSpec.BooleanValue replaceNyliumFungi; + public ModConfigSpec.BooleanValue cancelVanillaVillageTrees; + public ModConfigSpec.IntValue maxFallingTreeLeavesParticles; + + public ModConfigSpec.BooleanValue generatePodzol; + public ModConfigSpec.BooleanValue worldGen; + public ModConfigSpec.ConfigValue> dimensionBlacklist; + + public ModConfigSpec.BooleanValue generateDirtBucketRecipes; + public ModConfigSpec.BooleanValue generateMegaSeedRecipe; + public ModConfigSpec.ConfigValue biocharBrewingBase; + + public ModConfigSpec.ConfigValue preferredSeasonMod; + public ModConfigSpec.BooleanValue enableSeasonalSeedDrop; + public ModConfigSpec.BooleanValue enableSeasonalGrowth; + public ModConfigSpec.BooleanValue enableSeasonalFruitProduction; + public ModConfigSpec.DoubleValue wetSeasonOffset; + + public ModConfigSpec.BooleanValue debug; + + private DTConfigs() {} + + private static DTConfigs buildServerConfig(ModConfigSpec.Builder builder) { + DTConfigs config = new DTConfigs(); + + builder.push("seeds"); + config.leavesSeedDropRate = builder.defineInRange("leavesSeedDropRate", 1.0, 0.0, 64.0); + config.minSeasonalLeavesSeedDropRate = builder.defineInRange("minSeasonalLeavesSeedDropRate", 0.15, 0.0, 1.0); + config.voluntarySeedDropRate = builder.defineInRange("voluntarySeedDropRate", 0.01, 0.0, 1.0); + config.minSeasonalVoluntarySeedDropRate = builder.defineInRange("minSeasonalVoluntarySeedDropRate", 0.0, 0.0, 1.0); + config.seedPlantRate = builder.defineInRange("seedPlantRate", 1.0 / 6.0, 0.0, 1.0); + config.seedTimeToLive = builder.defineInRange("seedTimeToLive", 1200, 0, 6000); + config.seedOnlyForest = builder.define("seedOnlyForest", true); + config.seedMinForestness = builder.defineInRange("seedMinForestness", 0.0, 0.0, 1.0); + config.climateAffectsFruitsAndPods = builder.define("climateAffectsFruitsAndPods", true); + builder.pop(); + + builder.push("trees"); + config.treeGrowthMultiplier = builder.defineInRange("treeGrowthMultiplier", 0.5, 0, 16.0); + config.treeHarvestMultiplier = builder.defineInRange("treeHarvestMultiplier", 1.0, 0.0, 128.0); + config.maxTreeHardness = builder.defineInRange("maxTreeHardness", 20.0, 1.0, 200.0); + config.treeHardnessMultiplier = builder.defineInRange("treeHardnessMultiplier", 1.0, 1.0/128.0, 32.0); + config.dropSticks = builder.define("dropSticks", true); + config.scaleBiomeGrowthRate = builder.defineInRange("scaleBiomeGrowthRate", 0.5, 0.0, 1.0); + config.diseaseChance = builder.defineInRange("diseaseChance", 0.0, 0.0, 1.0); + config.maxBranchRotRadius = builder.defineInRange("maxBranchRotRadius", 7, 0, ThickBranchBlock.MAX_RADIUS_THICK); + config.rootyBlockHardnessMultiplier = builder.defineInRange("rootyBlockHardnessMultiplier", 40.0, 0.0, 128.0); + config.swampOaksInWater = builder.defineEnum("swampOaksInWater", SwampSpecies.WaterSurfaceGenerationState.ROOTED); + config.boneMealGrowthPulses = builder.defineInRange("boneMealGrowthPulses", 1, 1, 512); + builder.pop(); + + builder.push("interaction"); + config.isLeavesPassable = builder.define("isLeavesPassable", false); + config.vanillaLeavesCollision = builder.define("vanillaLeavesCollision", false); + config.enableBranchClimbing = builder.define("enableBranchClimbing", true); + config.enableCanopyCrash = builder.define("enableCanopyCrash", true); + config.axeDamageMode = builder.defineEnum("axeDamageMode", DynamicTrees.AxeDamage.THICKNESS); + config.enableFallingTrees = builder.define("enableFallingTrees", true); + config.enableFallingTreeDamage = builder.define("enableFallingTreeDamage", true); + config.fallingTreeDamageMultiplier = builder.defineInRange("fallingTreeDamageMultiplier", 1.0, 0.0, 100.0); + config.dirtBucketPlacesDirt = builder.define("dirtBucketPlacesDirt", true); + config.sloppyBreakDrops = builder.define("sloppyBreakDrops", false); + config.minRadiusForStrip = builder.defineInRange("minRadiusForStrip", 6, 0, 24); + config.enableStripRadiusReduction = builder.define("enableStripRadiusReduction", true); + config.canBoneMealFruit = builder.define("canBoneMealFruit", false); + config.canBoneMealPods = builder.define("canBoneMealPods", true); + config.dynamicSaplingDrops = builder.define("dynamicSaplingDrops", true); + builder.pop(); + + builder.push("world"); + config.generatePodzol = builder.define("generatePodzol", true); + config.worldGen = builder.define("worldGen", true); + config.dimensionBlacklist = builder.define("dimensionsBlacklist", new ArrayList<>()); + builder.pop(); + + builder.push("debug"); + config.debug = builder.define("debug", false); + builder.pop(); + + return config; + } + + private static DTConfigs buildCommonConfig(ModConfigSpec.Builder builder) { + DTConfigs config = new DTConfigs(); + + builder.push("vanilla"); + config.replaceVanillaSaplings = builder.define("replaceVanillaSaplings", false); + config.replaceNyliumFungi = builder.define("replaceNyliumFungi", true); + config.cancelVanillaVillageTrees = builder.define("cancelVanillaVillageTrees", true); + config.maxFallingTreeLeavesParticles = builder.defineInRange("maxFallingTreeLeavesParticles", 400, 0, 4096); + builder.pop(); + + builder.push("misc"); + config.generateDirtBucketRecipes = builder.define("generateDirtBucketRecipes", true); + config.generateMegaSeedRecipe = builder.define("generateMegaSeedRecipe", false); + config.biocharBrewingBase = builder.define("biocharBrewingBase", "minecraft:thick"); + builder.pop(); + + builder.push("integration"); + config.preferredSeasonMod = builder.define("preferredSeasonMod", SeasonCompatibilityHandler.ANY); + config.enableSeasonalSeedDrop = builder.define("enableSeasonalSeedDropFactor", true); + config.enableSeasonalGrowth = builder.define("enableSeasonalGrowthFactor", true); + config.enableSeasonalFruitProduction = builder.define("enableSeasonalFruitProductionFactor", true); + config.wetSeasonOffset = builder.defineInRange("wetSeasonOffset", 2.5, 0.0, 4.0); + builder.pop(); + + return config; + } + + private static DTConfigs buildClientConfig(ModConfigSpec.Builder builder) { + return new DTConfigs(); + } + +} diff --git a/common/src/main/java/com/dtteam/dynamictrees/data/DirtBucketRecipeHandler.java b/common/src/main/java/com/dtteam/dynamictrees/data/DirtBucketRecipeHandler.java index 322325bb4..b2ca19956 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/data/DirtBucketRecipeHandler.java +++ b/common/src/main/java/com/dtteam/dynamictrees/data/DirtBucketRecipeHandler.java @@ -1,8 +1,7 @@ package com.dtteam.dynamictrees.data; import com.dtteam.dynamictrees.DynamicTrees; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.registry.DTRegistries; import com.dtteam.dynamictrees.tree.species.Species; import net.minecraft.core.NonNullList; @@ -36,11 +35,11 @@ public static void registerDirtBucketRecipes(final Collection> c final ResourceLocation registryName = species.getRegistryName(); - if (Services.CONFIG.getBoolConfig(IConfigHelper.GENERATE_DIRT_BUCKET_RECIPES)){ + if (DTConfigs.COMMON.generateDirtBucketRecipes.get()){ generateSaplingRecipes(craftingRecipes, species, registryName); } - if (Services.CONFIG.getBoolConfig(IConfigHelper.GENERATE_MEGA_SEED_RECIPE)){ + if (DTConfigs.COMMON.generateMegaSeedRecipe.get()){ generateMegaSeedRecipes(craftingRecipes, species, registryName); } } diff --git a/common/src/main/java/com/dtteam/dynamictrees/entity/FallingTreeEntity.java b/common/src/main/java/com/dtteam/dynamictrees/entity/FallingTreeEntity.java index 0ce7fd2f4..2821aab69 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/entity/FallingTreeEntity.java +++ b/common/src/main/java/com/dtteam/dynamictrees/entity/FallingTreeEntity.java @@ -8,10 +8,10 @@ import com.dtteam.dynamictrees.entity.animation.AnimationHandler; import com.dtteam.dynamictrees.entity.animation.AnimationHandlers; import com.dtteam.dynamictrees.entity.animation.DataAnimationHandler; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.model.FallingTreeEntityModelTrackerCache; import com.dtteam.dynamictrees.model.ModelTracker; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; +import com.dtteam.dynamictrees.platform.*; import com.dtteam.dynamictrees.registry.DTRegistries; import com.dtteam.dynamictrees.tree.TreeHelper; import com.dtteam.dynamictrees.tree.species.Species; @@ -335,7 +335,7 @@ protected void updateNeighbors() { } protected AnimationHandler selectAnimationHandler() { - return Services.CONFIG.getBoolConfig(IConfigHelper.ENABLE_FALLING_TREES) ? destroyData.species.selectAnimationHandler(this) : AnimationHandlers.voidAnimationHandler; + return DTConfigs.SERVER.enableFallingTrees.get() ? destroyData.species.selectAnimationHandler(this) : AnimationHandlers.voidAnimationHandler; } public AnimationHandler defaultAnimationHandler() { diff --git a/common/src/main/java/com/dtteam/dynamictrees/entity/animation/FalloverAnimationHandler.java b/common/src/main/java/com/dtteam/dynamictrees/entity/animation/FalloverAnimationHandler.java index 0da40a58f..20181541c 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/entity/animation/FalloverAnimationHandler.java +++ b/common/src/main/java/com/dtteam/dynamictrees/entity/animation/FalloverAnimationHandler.java @@ -3,10 +3,9 @@ import com.dtteam.dynamictrees.api.network.BranchDestructionData; import com.dtteam.dynamictrees.block.branch.BranchBlock; import com.dtteam.dynamictrees.client.SoundInstanceHandler; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.data.tags.DTEntityTypeTags; import com.dtteam.dynamictrees.entity.FallingTreeEntity; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; import com.dtteam.dynamictrees.tree.TreeHelper; import com.dtteam.dynamictrees.tree.species.Species; import com.dtteam.dynamictrees.utility.MathUtils; @@ -107,7 +106,7 @@ private Vec3 rotateAroundAxis(Vec3 in, Vec3 axis, double theta){ protected void flingLeavesParticles(FallingTreeEntity entity, float fallSpeed){ int bounces = getData(entity).bounces; if (bounces > 1) return; - int maxParticleBlocks = Services.CONFIG.getIntConfig(IConfigHelper.MAX_FALLING_TREE_LEAVES_PARTICLES); + int maxParticleBlocks = DTConfigs.COMMON.maxFallingTreeLeavesParticles.get(); if (maxParticleBlocks == 0) return; BranchDestructionData data = entity.getDestroyData(); @@ -224,21 +223,19 @@ public void handleMotion(FallingTreeEntity entity) { //Crush living things with clumsy dead trees Level level = entity.level(); - if (Services.CONFIG.getBoolConfig(IConfigHelper.ENABLE_FALLING_TREE_DAMAGE) && !level.isClientSide) { + if (DTConfigs.SERVER.enableFallingTreeDamage.get() && !level.isClientSide) { List elist = testEntityCollision(entity); for (LivingEntity living : elist) { if (!getData(entity).entitiesHit.contains(living) && !living.getType().is(DTEntityTypeTags.FALLING_TREE_DAMAGE_IMMUNE)) { getData(entity).entitiesHit.add(living); float damage = entity.getDestroyData().woodVolume.getVolume() * Math.abs(fallSpeed) * 3f; if (getData(entity).bounces == 0 && damage > 2) { - //System.out.println("damage: " + damage); living.setDeltaMovement( living.getDeltaMovement().x + (level.random.nextFloat() * entity.getDestroyData().toolDir.getOpposite().getStepX() * damage * 0.2f), living.getDeltaMovement().y + (level.random.nextFloat() * fallSpeed * 0.25f), living.getDeltaMovement().z + (level.random.nextFloat() * entity.getDestroyData().toolDir.getOpposite().getStepZ() * damage * 0.2f)); living.setDeltaMovement(living.getDeltaMovement().x + (level.random.nextFloat() - 0.5), living.getDeltaMovement().y, living.getDeltaMovement().z + (level.random.nextFloat() - 0.5)); - damage *= Services.CONFIG.getDoubleConfig(IConfigHelper.FALLING_TREE_DAMAGE_MULTIPLIER); - //System.out.println("Tree Falling Damage: " + damage + "/" + living.getHealth()); + damage *= DTConfigs.SERVER.fallingTreeDamageMultiplier.get(); living.hurt(AnimationConstants.treeDamage(level.registryAccess()), damage); } } diff --git a/common/src/main/java/com/dtteam/dynamictrees/item/DirtBucket.java b/common/src/main/java/com/dtteam/dynamictrees/item/DirtBucket.java index 2d5ee0b0b..605094c47 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/item/DirtBucket.java +++ b/common/src/main/java/com/dtteam/dynamictrees/item/DirtBucket.java @@ -1,7 +1,6 @@ package com.dtteam.dynamictrees.item; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.registry.DTRegistries; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -43,7 +42,7 @@ public InteractionResultHolder use(Level world, Player player, Intera } } - if (Services.CONFIG.getBoolConfig(IConfigHelper.DIRT_BUCKET_PLACES_DIRT)) { + if (DTConfigs.SERVER.dirtBucketPlacesDirt.get()) { if (blockRayTraceResult.getType() != HitResult.Type.BLOCK) { return new InteractionResultHolder<>(InteractionResult.PASS, itemStack); } else { diff --git a/common/src/main/java/com/dtteam/dynamictrees/item/Seed.java b/common/src/main/java/com/dtteam/dynamictrees/item/Seed.java index 2b260ae08..b6bab4adb 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/item/Seed.java +++ b/common/src/main/java/com/dtteam/dynamictrees/item/Seed.java @@ -3,8 +3,8 @@ import com.dtteam.dynamictrees.api.lazyvalue.LazyValue; import com.dtteam.dynamictrees.api.worldgen.LevelContext; import com.dtteam.dynamictrees.block.sapling.PottedSaplingBlock; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; +import com.dtteam.dynamictrees.config.DTConfigs; +import com.dtteam.dynamictrees.platform.*; import com.dtteam.dynamictrees.registry.DTRegistries; import com.dtteam.dynamictrees.tree.species.Species; import com.dtteam.dynamictrees.worldgen.DynamicTreeGenerationContext; @@ -122,9 +122,9 @@ public boolean shouldPlant(Level level, BlockPos pos, ItemStack seedStack) { return false; } - float plantChance = (float) (getSpecies().biomeSuitability(level, pos) * Services.CONFIG.getDoubleConfig(IConfigHelper.SEED_PLANT_RATE)); + float plantChance = (float) (getSpecies().biomeSuitability(level, pos) * DTConfigs.SERVER.seedPlantRate.get()); - if (Services.CONFIG.getBoolConfig(IConfigHelper.SEED_ONLY_FOREST)) { + if (DTConfigs.SERVER.seedOnlyForest.get()) { // plantChance *= BiomeDatabases.getDimensionalOrDefault(level.dimension().location()) // .getForestness(level.getBiome(pos)); } @@ -150,7 +150,7 @@ public boolean hasForcePlant(ItemStack seedStack) { } public int getTimeToLive(ItemStack seedStack) { - int lifespan = Services.CONFIG.getIntConfig(IConfigHelper.SEED_TIME_TO_LIVE);//1 minute by default(helps with lag) + int lifespan = DTConfigs.SERVER.seedTimeToLive.get(); // if (seedStack.hasTag()) { // CompoundTag nbtData = seedStack.getTag(); // assert nbtData != null; diff --git a/common/src/main/java/com/dtteam/dynamictrees/loot/condition/SeasonalSeedDropChance.java b/common/src/main/java/com/dtteam/dynamictrees/loot/condition/SeasonalSeedDropChance.java index 6096a218e..44527b540 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/loot/condition/SeasonalSeedDropChance.java +++ b/common/src/main/java/com/dtteam/dynamictrees/loot/condition/SeasonalSeedDropChance.java @@ -1,8 +1,7 @@ package com.dtteam.dynamictrees.loot.condition; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.loot.DTLootContextParams; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; import com.dtteam.dynamictrees.registry.DTRegistries; import com.mojang.datafixers.util.Unit; import com.mojang.serialization.Codec; @@ -34,10 +33,9 @@ public LootItemConditionType getType() { public boolean test(LootContext context) { Float seasonalSeedDropFactor = context.getParamOrNull(DTLootContextParams.SEASONAL_SEED_DROP_FACTOR); assert seasonalSeedDropFactor != null; - // Adjusted to a minimum of 0.15 to ensure there are at least some seed drops all year round. - double minimumDropRate = Services.CONFIG.getDoubleConfig(IConfigHelper.MIN_SEASONAL_LEAVES_SEED_DROP_RATE); + double minimumDropRate = DTConfigs.SERVER.minSeasonalLeavesSeedDropRate.get(); double adjustedSeasonalSeedDropFactor = Math.min(seasonalSeedDropFactor + minimumDropRate, 1.0F); - return Services.CONFIG.getDoubleConfig(IConfigHelper.LEAVES_SEED_DROP_RATE) * adjustedSeasonalSeedDropFactor > context.getRandom().nextFloat(); + return DTConfigs.SERVER.leavesSeedDropRate.get() * adjustedSeasonalSeedDropFactor > context.getRandom().nextFloat(); } public static LootItemCondition.Builder seasonalSeedDropChance() { diff --git a/common/src/main/java/com/dtteam/dynamictrees/loot/condition/VoluntarySeedDropChance.java b/common/src/main/java/com/dtteam/dynamictrees/loot/condition/VoluntarySeedDropChance.java index ce0a1470d..77c517b45 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/loot/condition/VoluntarySeedDropChance.java +++ b/common/src/main/java/com/dtteam/dynamictrees/loot/condition/VoluntarySeedDropChance.java @@ -1,8 +1,7 @@ package com.dtteam.dynamictrees.loot.condition; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.loot.DTLootContextParams; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; import com.dtteam.dynamictrees.registry.DTRegistries; import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; @@ -36,9 +35,9 @@ public LootItemConditionType getType() { public boolean test(LootContext context) { final Float seasonalSeedDropFactor = context.getParamOrNull(DTLootContextParams.SEASONAL_SEED_DROP_FACTOR); assert seasonalSeedDropFactor != null; - double minimumDropRate = Services.CONFIG.getDoubleConfig(IConfigHelper.MIN_SEASONAL_VOLUNTARY_SEED_DROP_RATE); + double minimumDropRate = DTConfigs.SERVER.minSeasonalVoluntarySeedDropRate.get(); double adjustedSeasonalSeedDropFactor = Math.min(seasonalSeedDropFactor + minimumDropRate, 1.0F); - return rarity * Services.CONFIG.getDoubleConfig(IConfigHelper.VOLUNTARY_SEED_DROP_RATE) * adjustedSeasonalSeedDropFactor > context.getRandom().nextFloat(); + return rarity * DTConfigs.SERVER.voluntarySeedDropRate.get() * adjustedSeasonalSeedDropFactor > context.getRandom().nextFloat(); } public static LootItemCondition.Builder voluntarySeedDropChance() { diff --git a/common/src/main/java/com/dtteam/dynamictrees/systems/genfeature/PodzolGenFeature.java b/common/src/main/java/com/dtteam/dynamictrees/systems/genfeature/PodzolGenFeature.java index fa90ce173..d2bce9b8b 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/systems/genfeature/PodzolGenFeature.java +++ b/common/src/main/java/com/dtteam/dynamictrees/systems/genfeature/PodzolGenFeature.java @@ -6,9 +6,8 @@ import com.dtteam.dynamictrees.block.branch.TrunkShellBlock; import com.dtteam.dynamictrees.block.soil.SoilBlock; import com.dtteam.dynamictrees.block.soil.SoilHelper; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.data.tags.DTBlockTags; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; import com.dtteam.dynamictrees.systems.genfeature.context.PostGrowContext; import com.dtteam.dynamictrees.systems.nodemapper.FindEndsNode; import com.dtteam.dynamictrees.tree.TreeHelper; @@ -54,7 +53,7 @@ protected GenFeatureConfiguration createDefaultConfiguration() { @Override protected boolean postGrow(GenFeatureConfiguration configuration, PostGrowContext context) { - if (!Services.CONFIG.getBoolConfig(IConfigHelper.GENERATE_PODZOL)) return false; + if (!DTConfigs.SERVER.generatePodzol.get()) return false; final LevelAccessor level = context.level(); final FindEndsNode endFinder = new FindEndsNode(); diff --git a/common/src/main/java/com/dtteam/dynamictrees/systems/season/ActiveSeasonGrowthCalculator.java b/common/src/main/java/com/dtteam/dynamictrees/systems/season/ActiveSeasonGrowthCalculator.java index de7060b09..e983cbb1f 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/systems/season/ActiveSeasonGrowthCalculator.java +++ b/common/src/main/java/com/dtteam/dynamictrees/systems/season/ActiveSeasonGrowthCalculator.java @@ -3,8 +3,7 @@ import com.dtteam.dynamictrees.api.season.ClimateZoneType; import com.dtteam.dynamictrees.api.season.SeasonGrowthCalculator; import com.dtteam.dynamictrees.api.season.SeasonType; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; +import com.dtteam.dynamictrees.config.DTConfigs; import net.minecraft.util.Mth; public class ActiveSeasonGrowthCalculator implements SeasonGrowthCalculator { @@ -15,7 +14,7 @@ protected float clippedSineWave(float seasonValue, float qPhase, float amplitude private float peakClimateOffset(SeasonType type){ float summerOffset = -0.5f; - float wetSeasonOffset = (float)(summerOffset + Services.CONFIG.getDoubleConfig(IConfigHelper.WET_SEASON_OFFSET)); + float wetSeasonOffset = (float)(summerOffset + DTConfigs.COMMON.wetSeasonOffset.get()); if (type == SeasonType.DRY_WET){ return wetSeasonOffset; } else return summerOffset; diff --git a/common/src/main/java/com/dtteam/dynamictrees/systems/season/SeasonCompatibilityHandler.java b/common/src/main/java/com/dtteam/dynamictrees/systems/season/SeasonCompatibilityHandler.java index a24472f36..fb723f8ae 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/systems/season/SeasonCompatibilityHandler.java +++ b/common/src/main/java/com/dtteam/dynamictrees/systems/season/SeasonCompatibilityHandler.java @@ -2,8 +2,8 @@ import com.dtteam.dynamictrees.DynamicTrees; import com.dtteam.dynamictrees.api.season.SeasonManager; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; import com.google.common.collect.Maps; import org.apache.logging.log4j.LogManager; @@ -60,7 +60,7 @@ public static void registerBuiltInSeasonManagers() { public static final String ANY = "*"; public static void reloadSeasonManager() { - final String modId = Services.CONFIG.getStringConfig(IConfigHelper.PREFERRED_SEASON_MOD); + final String modId = DTConfigs.COMMON.preferredSeasonMod.get(); // If disabled, use null manager. if (Objects.equals(modId, DISABLED)) { diff --git a/common/src/main/java/com/dtteam/dynamictrees/systems/season/SeasonHelper.java b/common/src/main/java/com/dtteam/dynamictrees/systems/season/SeasonHelper.java index fb2a3192a..cb338b717 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/systems/season/SeasonHelper.java +++ b/common/src/main/java/com/dtteam/dynamictrees/systems/season/SeasonHelper.java @@ -3,8 +3,7 @@ import com.dtteam.dynamictrees.api.season.ClimateZoneType; import com.dtteam.dynamictrees.api.season.SeasonManager; import com.dtteam.dynamictrees.api.worldgen.LevelContext; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; +import com.dtteam.dynamictrees.config.DTConfigs; import net.minecraft.core.BlockPos; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; @@ -29,22 +28,22 @@ static public void updateTick(Level level, long dayTime) { } static public float globalSeasonalGrowthFactor(LevelContext levelContext, BlockPos rootPos, float offset) { - return Services.CONFIG.getBoolConfig(IConfigHelper.ENABLE_SEASONAL_GROWTH) + return DTConfigs.COMMON.enableSeasonalGrowth.get() ? SeasonCompatibilityHandler.getSeasonManager().getGrowthFactor(levelContext.level(), rootPos, offset) : 1.0F; } static public float globalSeasonalSeedDropFactor(LevelContext levelContext, BlockPos pos, float offset) { - return Services.CONFIG.getBoolConfig(IConfigHelper.ENABLE_SEASONAL_SEED_DROP) + return DTConfigs.COMMON.enableSeasonalSeedDrop.get() ? SeasonCompatibilityHandler.getSeasonManager().getSeedDropFactor(levelContext.level(), pos, offset) : 1.0F; } static public float globalSeasonalFruitProductionFactor(LevelContext levelContext, BlockPos pos, float offset) { - return Services.CONFIG.getBoolConfig(IConfigHelper.ENABLE_SEASONAL_SEED_FRUIT_PRODUCTION) + return DTConfigs.COMMON.enableSeasonalFruitProduction.get() ? SeasonCompatibilityHandler.getSeasonManager().getFruitProductionFactor(levelContext.level(), pos, offset) : 1.0F; } static public Float getPeakFruitProductionSeason(LevelContext levelContext, BlockPos pos, float offset) { - return Services.CONFIG.getBoolConfig(IConfigHelper.ENABLE_SEASONAL_SEED_FRUIT_PRODUCTION) + return DTConfigs.COMMON.enableSeasonalFruitProduction.get() ? SeasonCompatibilityHandler.getSeasonManager().getPeakFruitProductionSeasonValue(levelContext.level(), pos, offset) : null; } diff --git a/common/src/main/java/com/dtteam/dynamictrees/tree/family/Family.java b/common/src/main/java/com/dtteam/dynamictrees/tree/family/Family.java index b7cd7ad1b..4cd017b06 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/tree/family/Family.java +++ b/common/src/main/java/com/dtteam/dynamictrees/tree/family/Family.java @@ -19,8 +19,8 @@ import com.dtteam.dynamictrees.data.tags.DTItemTags; import com.dtteam.dynamictrees.entity.FallingTreeEntity; import com.dtteam.dynamictrees.entity.animation.AnimationHandler; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; import com.dtteam.dynamictrees.systems.cell.MetadataCell; import com.dtteam.dynamictrees.tree.TreeHelper; import com.dtteam.dynamictrees.tree.species.Species; @@ -630,7 +630,7 @@ public void setHasStrippedBranch(boolean hasStrippedBranch) { } public int getMinRadiusForStripping() { - if (minRadiusForStripping == null) return Services.CONFIG.getIntConfig(IConfigHelper.MIN_RADIUS_FOR_STRIP); + if (minRadiusForStripping == null) return DTConfigs.SERVER.minRadiusForStrip.get(); return minRadiusForStripping; } @@ -639,7 +639,7 @@ public void setMinRadiusForStripping(int radius) { } public boolean reduceRadiusWhenStripping() { - if (Services.CONFIG.getBoolConfig(IConfigHelper.ENABLE_STRIP_RADIUS_REDUCTION)) + if (DTConfigs.SERVER.enableStripRadiusReduction.get()) return reduceRadiusWhenStripping; return false; } diff --git a/common/src/main/java/com/dtteam/dynamictrees/tree/species/Species.java b/common/src/main/java/com/dtteam/dynamictrees/tree/species/Species.java index 489c6c806..0d324ae0c 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/tree/species/Species.java +++ b/common/src/main/java/com/dtteam/dynamictrees/tree/species/Species.java @@ -41,8 +41,8 @@ import com.dtteam.dynamictrees.loot.DTLootParameterSets; import com.dtteam.dynamictrees.loot.entry.SeedItemLootPoolEntry; import com.dtteam.dynamictrees.model.FallingTreeEntityModel; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; import com.dtteam.dynamictrees.registry.DTRegistries; import com.dtteam.dynamictrees.systems.GrowSignal; import com.dtteam.dynamictrees.systems.SeedSaplingRecipe; @@ -647,8 +647,8 @@ public List getBranchesDrops(Level level, NetVolumeNode.Volume volume } public void processVolume(NetVolumeNode.Volume volume) { - volume.multiplyVolume(Services.CONFIG.getDoubleConfig(IConfigHelper.TREE_HARVEST_MULTIPLIER)); // For cheaters. you know who you are. - volume.multiplyVolume(getFamily().getLootVolumeMultiplier()); //the family can have a multiplier too + volume.multiplyVolume(DTConfigs.SERVER.treeHarvestMultiplier.get()); + volume.multiplyVolume(getFamily().getLootVolumeMultiplier()); } private List getDropsForBranchType(Level level, ItemStack tool, @Nullable Float explosionRadius, @@ -694,7 +694,7 @@ public static class LogsAndSticks { public LogsAndSticks(List logs, int sticks) { this.logs = logs; - this.sticks = Services.CONFIG.getBoolConfig(IConfigHelper.DROP_STICKS) ? sticks : 0; + this.sticks = DTConfigs.SERVER.dropSticks.get() ? sticks : 0; } } @@ -1477,7 +1477,7 @@ public boolean rot(LevelAccessor level, BlockPos pos, int neighborCount, int rad } } - int maxBranchRotRadius = Services.CONFIG.getIntConfig(IConfigHelper.MAX_BRANCH_ROT_RADIUS); + int maxBranchRotRadius = DTConfigs.SERVER.maxBranchRotRadius.get(); if (rapid || (maxBranchRotRadius != 0 && radius <= maxBranchRotRadius)) { BranchBlock branch = TreeHelper.getBranch(level.getBlockState(pos)); if (branch != null) { @@ -1527,7 +1527,7 @@ public float rotChance(LevelAccessor level, BlockPos pos, RandomSource rand, int */ public boolean grow(Level level, SoilBlock rootyDirt, BlockPos rootPos, int fertility, TreePart treeBase, BlockPos treePos, RandomSource random, boolean natural) { - float growthRate = (float) (getGrowthRate(level, rootPos) * Services.CONFIG.getDoubleConfig(IConfigHelper.TREE_GROWTH_MULTIPLIER)); + float growthRate = (float) (getGrowthRate(level, rootPos) * DTConfigs.SERVER.treeGrowthMultiplier.get()); do { if (fertility > 0) { if (growthRate > random.nextFloat()) { @@ -1615,7 +1615,7 @@ public boolean postGrow(Level level, BlockPos rootPos, BlockPos treePos, int fer * @return true if the tree became diseased */ public boolean handleDisease(Level level, TreePart baseTreePart, BlockPos treePos, RandomSource random, int fertility) { - if (fertility == 0 && Services.CONFIG.getDoubleConfig(IConfigHelper.DISEASE_CHANCE) > random.nextFloat()) { + if (fertility == 0 && DTConfigs.SERVER.diseaseChance.get() > random.nextFloat()) { baseTreePart.analyse(level.getBlockState(treePos), level, treePos, Direction.DOWN, new MapSignal(new DiseaseNode(this))); return true; } @@ -1665,7 +1665,7 @@ public float biomeSuitability(Level level, BlockPos pos) { return result.suitability(); } - double ugs = Services.CONFIG.getDoubleConfig(IConfigHelper.SCALE_BIOME_GROWTH_RATE); // Universal growth scalar. + double ugs = DTConfigs.SERVER.scaleBiomeGrowthRate.get(); if (ugs == 1.0 || preferredClimate == ClimateZoneType.NONE) { return 1.0f; @@ -1816,7 +1816,7 @@ public void inheritSeasonalFruitingParametersToFruits(){ this.fruits.forEach((fruit)->{ fruit.setSeasonalFactorGetter((l, p)->{ ClimateZoneType climate = ClimateHelper.getClimate(l.level(), p); - double multiplier = Services.CONFIG.getBoolConfig(IConfigHelper.CLIMATE_AFFECTS_FRUITS_AND_PODS) + double multiplier = DTConfigs.SERVER.climateAffectsFruitsAndPods.get() ? ClimateHelper.climateMultiplier(this, climate, climateTolerance) : 1.0; return (float)(this.seasonalFruitProductionFactor(l,p) * multiplier); }); @@ -1828,7 +1828,7 @@ public void inheritSeasonalFruitingParametersToPods(){ this.pods.forEach((fruit)->{ fruit.setSeasonalFactorGetter((l, p)->{ ClimateZoneType climate = ClimateHelper.getClimate(l.level(), p); - double multiplier = Services.CONFIG.getBoolConfig(IConfigHelper.CLIMATE_AFFECTS_FRUITS_AND_PODS) + double multiplier = DTConfigs.SERVER.climateAffectsFruitsAndPods.get() ? ClimateHelper.climateMultiplier(this, climate, climateTolerance) : 1.0; return (float)(this.seasonalFruitProductionFactor(l,p) * multiplier); }); @@ -1843,7 +1843,7 @@ public int getSeasonalTooltipFlags(LevelContext levelContext, Player player) { if (showSeasonalTooltip()) { BlockPos playerPos = BlockPos.containing(player.position()); ClimateZoneType climate = ClimateHelper.getClimate(player.level(), playerPos); - float suitability = (float) (Services.CONFIG.getBoolConfig(IConfigHelper.CLIMATE_AFFECTS_FRUITS_AND_PODS) + float suitability = (float) (DTConfigs.SERVER.climateAffectsFruitsAndPods.get() ? ClimateHelper.climateMultiplier(this, climate, climateTolerance) : 1.0); if (suitability < 0.3) return 0; //No seasons, still display @@ -1896,7 +1896,7 @@ public SubstanceEffect getSubstanceEffect(ItemStack itemStack) { // Bonemeal fertilizes the soil and causes a single growth pulse. if (canBoneMealTree() && itemStack.is(DTItemTags.FERTILIZER)) { - return new FertilizeSubstance().setAmount(2).setGrow(true).setPulses(Services.CONFIG.getIntConfig(IConfigHelper.BONE_MEAL_GROWTH_PULSES)); + return new FertilizeSubstance().setAmount(2).setGrow(true).setPulses(DTConfigs.SERVER.boneMealGrowthPulses.get()); } // Use substance provider interface if it's available. diff --git a/common/src/main/java/com/dtteam/dynamictrees/tree/species/SwampSpecies.java b/common/src/main/java/com/dtteam/dynamictrees/tree/species/SwampSpecies.java index 366877f5c..3125941c6 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/tree/species/SwampSpecies.java +++ b/common/src/main/java/com/dtteam/dynamictrees/tree/species/SwampSpecies.java @@ -2,8 +2,7 @@ import com.dtteam.dynamictrees.api.registry.TypedRegistry; import com.dtteam.dynamictrees.block.leaves.LeavesProperties; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.tree.family.Family; import com.dtteam.dynamictrees.worldgen.DynamicTreeGenerationContext; import net.minecraft.core.Direction; @@ -28,7 +27,7 @@ public SwampSpecies(ResourceLocation name, Family family, LeavesProperties leave @Override public boolean generate(DynamicTreeGenerationContext context) { if (isWater(context.level().getBlockState(context.rootPos()))) { - switch (Services.CONFIG.getEnumConfig(IConfigHelper.SWAMP_OAKS_IN_WATER, WaterSurfaceGenerationState.class)) { + switch (DTConfigs.SERVER.swampOaksInWater.get()) { case WaterSurfaceGenerationState.SUNK: //generate 1 block down if (context.radius() >= minRadiusForSunkGeneration) { context.rootPos().move(Direction.DOWN, countWaterBlocksBelow(context.level(), context.rootPos(), getAllowedWaterHeightForWorldgen())); diff --git a/common/src/main/java/com/dtteam/dynamictrees/treepack/Resources.java b/common/src/main/java/com/dtteam/dynamictrees/treepack/Resources.java index a796d3d18..65ef38e61 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/treepack/Resources.java +++ b/common/src/main/java/com/dtteam/dynamictrees/treepack/Resources.java @@ -3,9 +3,9 @@ import com.dtteam.dynamictrees.DynamicTrees; import com.dtteam.dynamictrees.api.configuration.ConfigurationTemplateResourceLoader; import com.dtteam.dynamictrees.api.resource.TreeResourceManager; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.data.DirtBucketRecipeHandler; import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; import com.dtteam.dynamictrees.systems.genfeature.GenFeature; import com.dtteam.dynamictrees.systems.genfeature.GenFeatureConfiguration; import com.dtteam.dynamictrees.systems.growthlogic.GrowthLogicKit; @@ -165,8 +165,8 @@ public CompletableFuture reload(PreparationBarrier stage, ResourceManager } private void registerDirtBucketRecipes() { - if (!Services.CONFIG.getBoolConfig(IConfigHelper.GENERATE_DIRT_BUCKET_RECIPES) - && !Services.CONFIG.getBoolConfig(IConfigHelper.GENERATE_MEGA_SEED_RECIPE)) { + if (!DTConfigs.COMMON.generateDirtBucketRecipes.get() + && !DTConfigs.COMMON.generateMegaSeedRecipe.get()) { return; } diff --git a/common/src/main/java/com/dtteam/dynamictrees/utility/ItemUtils.java b/common/src/main/java/com/dtteam/dynamictrees/utility/ItemUtils.java index a7b876572..e4975784b 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/utility/ItemUtils.java +++ b/common/src/main/java/com/dtteam/dynamictrees/utility/ItemUtils.java @@ -1,8 +1,8 @@ package com.dtteam.dynamictrees.utility; import com.dtteam.dynamictrees.DynamicTrees; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; import com.dtteam.dynamictrees.systems.nodemapper.NetVolumeNode; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; @@ -72,7 +72,7 @@ public static void damageAxe(final LivingEntity entity, @Nullable final ItemStac return; } - int damage = switch (Services.CONFIG.getEnumConfig(IConfigHelper.AXE_DAMAGE_MODE, DynamicTrees.AxeDamage.class)) { + int damage = switch (DTConfigs.SERVER.axeDamageMode.get()) { case VANILLA -> 1; case THICKNESS -> Math.max(1, radius) / 2; case VOLUME -> (int) woodVolume.getVolume(); diff --git a/common/src/main/java/com/dtteam/dynamictrees/worldgen/BiomeDatabase.java b/common/src/main/java/com/dtteam/dynamictrees/worldgen/BiomeDatabase.java index 8be1e6567..52d7db9c6 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/worldgen/BiomeDatabase.java +++ b/common/src/main/java/com/dtteam/dynamictrees/worldgen/BiomeDatabase.java @@ -2,9 +2,8 @@ import com.dtteam.dynamictrees.api.worldgen.BiomePropertySelectors; import com.dtteam.dynamictrees.api.worldgen.BiomePropertySelectors.*; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.deserialization.JsonDeserializers; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; import com.google.common.collect.Maps; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -570,7 +569,7 @@ public Function getMultipass(Holder biome) { } public BiomeDatabase setForestness(Holder biome, float forestness) { - getEntry(biome).setForestness((float) Math.max(forestness, Services.CONFIG.getDoubleConfig(IConfigHelper.SEED_MIN_FORESTNESS))); + getEntry(biome).setForestness((float) Math.max(forestness, DTConfigs.SERVER.seedMinForestness.get())); return this; } diff --git a/common/src/main/java/com/dtteam/dynamictrees/worldgen/BiomeDatabases.java b/common/src/main/java/com/dtteam/dynamictrees/worldgen/BiomeDatabases.java index 2193671cc..9438e5626 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/worldgen/BiomeDatabases.java +++ b/common/src/main/java/com/dtteam/dynamictrees/worldgen/BiomeDatabases.java @@ -1,7 +1,6 @@ package com.dtteam.dynamictrees.worldgen; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; +import com.dtteam.dynamictrees.config.DTConfigs; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import net.minecraft.ResourceLocationException; @@ -45,8 +44,8 @@ public static boolean isBlacklisted(ResourceLocation dimensionLocation) { } public static void populateBlacklistFromConfig() { - if (Services.CONFIG.isServerConfigLoaded()){ - Services.CONFIG.getStringListConfig(IConfigHelper.DIMENSION_BLACK_LIST).forEach(BiomeDatabases::tryBlacklist); + if (DTConfigs.SERVER_CONFIG.isLoaded()){ + DTConfigs.SERVER.dimensionBlacklist.get().forEach(BiomeDatabases::tryBlacklist); } else { LogManager.getLogger().error("Dimension Blacklist tried to load from config before config was loaded! this should not happen."); } diff --git a/common/src/main/java/com/dtteam/dynamictrees/worldgen/feature/DTReplaceNyliumFungiBlockStateProvider.java b/common/src/main/java/com/dtteam/dynamictrees/worldgen/feature/DTReplaceNyliumFungiBlockStateProvider.java index e304577b6..1f93cdda9 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/worldgen/feature/DTReplaceNyliumFungiBlockStateProvider.java +++ b/common/src/main/java/com/dtteam/dynamictrees/worldgen/feature/DTReplaceNyliumFungiBlockStateProvider.java @@ -1,7 +1,6 @@ package com.dtteam.dynamictrees.worldgen.feature; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.registry.DTRegistries; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; @@ -31,7 +30,7 @@ protected BlockStateProviderType type() { @Override public BlockState getState(RandomSource random, BlockPos state) { - return Services.CONFIG.getBoolConfig(IConfigHelper.REPLACE_NYLIUM_FUNGI) + return DTConfigs.COMMON.replaceNyliumFungi.get() ? this.enabled.getState(random, state) : this.disabled.getState(random, state); } diff --git a/common/src/main/java/com/dtteam/dynamictrees/worldgen/feature/DynamicTreeFeature.java b/common/src/main/java/com/dtteam/dynamictrees/worldgen/feature/DynamicTreeFeature.java index 347101e7a..d2e5c019f 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/worldgen/feature/DynamicTreeFeature.java +++ b/common/src/main/java/com/dtteam/dynamictrees/worldgen/feature/DynamicTreeFeature.java @@ -5,9 +5,8 @@ import com.dtteam.dynamictrees.api.worldgen.LevelContext; import com.dtteam.dynamictrees.api.worldgen.RandomXOR; import com.dtteam.dynamictrees.block.soil.SoilBlock; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.data.tags.DTBlockTags; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; import com.dtteam.dynamictrees.systems.poissondisc.PoissonDisc; import com.dtteam.dynamictrees.systems.poissondisc.UniversalPoissonDiscProvider; import com.dtteam.dynamictrees.tree.species.Species; @@ -138,7 +137,7 @@ protected GeneratorResult generateTree(LevelContext levelContext, BiomeDatabase. } // Display concrete circles for testing the circle growing algorithm. - if (Services.CONFIG.getBoolConfig(IConfigHelper.DEBUG)) { + if (DTConfigs.SERVER.debug.get()) { this.generateConcreteCircle(levelContext.accessor(), circle, groundPos.getY(), result); } diff --git a/common/src/main/java/com/dtteam/dynamictrees/worldgen/structure/DTCancelVanillaTreePoolElement.java b/common/src/main/java/com/dtteam/dynamictrees/worldgen/structure/DTCancelVanillaTreePoolElement.java index ac26a1490..ca1cf42ab 100644 --- a/common/src/main/java/com/dtteam/dynamictrees/worldgen/structure/DTCancelVanillaTreePoolElement.java +++ b/common/src/main/java/com/dtteam/dynamictrees/worldgen/structure/DTCancelVanillaTreePoolElement.java @@ -1,7 +1,6 @@ package com.dtteam.dynamictrees.worldgen.structure; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.registry.DTRegistries; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; @@ -44,7 +43,7 @@ public static FunctionvS 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 BgTw#; diff --git a/fabric/libs/toml-3.6.7.jar b/fabric/libs/toml-3.6.7.jar deleted file mode 100644 index e9e928ef561629c4bc0edd498e7d11cc911c3e6d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34119 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 yUjeWk{xQJ+YX*Nr (default, cached) - public HashMap, ?>> getDefaultValues() { - return defaultValues; - } - - private final HashMap, ?>> defaultValues = new HashMap<>(); - - public void addKeyValuePair(Pair keyValuePair, String comment, @Nullable Pair range) { - defaultValues.put(keyValuePair.getFirst(), new Pair<>(keyValuePair.getSecond().getClass(), keyValuePair.getSecond())); - configContents += - " # " + comment + "\n" + - " # Default: " + keyValuePair.getSecond() + "\n" + - (range == null ? "" : " # Range: " + range.getFirst() + " ~ " + range.getSecond() + "\n") + - " " + keyValuePair.getFirst() + "=" + keyValuePair.getSecond() + "\n"; - } - - public void addSection(String name){ - configContents += "\n[" + name + "]\n"; - } - - @Override - public String get(String namespace) { - return configContents; - } -} \ No newline at end of file diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/config/DTConfigs.java b/fabric/src/main/java/com/dtteam/dynamictrees/config/DTConfigs.java deleted file mode 100644 index 7f7057936..000000000 --- a/fabric/src/main/java/com/dtteam/dynamictrees/config/DTConfigs.java +++ /dev/null @@ -1,159 +0,0 @@ -package com.dtteam.dynamictrees.config; - -import com.dtteam.dynamictrees.DynamicTrees; -import com.dtteam.dynamictrees.block.branch.ThickBranchBlock; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; -import com.dtteam.dynamictrees.systems.season.SeasonCompatibilityHandler; -import com.dtteam.dynamictrees.tree.species.SwampSpecies; -import com.mojang.datafixers.util.Pair; - -public class DTConfigs { - public static SimpleConfig CONFIG; - private static DTConfigProvider configs; - public static boolean isLoaded = false; - - public static void registerConfigs() { - configs = new DTConfigProvider(); - createConfigs(); - - CONFIG = SimpleConfig.of(DynamicTrees.MOD_ID).provider(configs).request(); - isLoaded = true; - } - - private static void createConfigs() { - configs.addSection("seeds"); - createConfig("The rate at which seeds drop from leaves.", - IConfigHelper.LEAVES_SEED_DROP_RATE, 1D, 0D, 64D); - createConfig("The minimum chance for seed dropping from leaves when a seasonal mod is installed. 0 = during the off season seeds never drop from leaves, 1 = seeds will drop at maximum rate during the entire year. Can be fractional.", - IConfigHelper.MIN_SEASONAL_LEAVES_SEED_DROP_RATE, 0.15, 0.0, 1.0); - createConfig("The rate at which seeds voluntarily drop from branches", - IConfigHelper.VOLUNTARY_SEED_DROP_RATE, 0.01, 0.0, 1.0); - createConfig("The minimum chance for seed dropping voluntarily when a seasonal mod is installed. 0 = during the off season seeds never drop voluntarily, 1 = seeds will drop at maximum rate during the entire year. Can be fractional.", - IConfigHelper.MIN_SEASONAL_VOLUNTARY_SEED_DROP_RATE, 0.0, 0.0, 1.0); - createConfig("The rate at which seeds voluntarily plant themselves in their ideal biomes", - IConfigHelper.SEED_PLANT_RATE, 1f / 6f, 0.0, 1.0); - createConfig("Ticks before a seed in the world attempts to plant itself or despawn. 1200 = 1 minute", - IConfigHelper.SEED_TIME_TO_LIVE, 1200, 0, 6000); - createConfig("If enabled then seeds will only voluntarily plant themselves in forest-like biomes.", - IConfigHelper.SEED_ONLY_FOREST, true); - createConfig("The minimum forestness that non-forest-like biomes can have. 0 = is not at all a forest, 1 = may as well be a forest. Can be fractional.", - IConfigHelper.SEED_MIN_FORESTNESS, 0.0, 0.0, 1.0); - createConfig("If enabled, fruit and pod production will be affected by the current biome's climate.", - IConfigHelper.CLIMATE_AFFECTS_FRUITS_AND_PODS, true); - - configs.addSection("trees"); - createConfig("Factor that multiplies the rate at which trees grow. Use at own risk", - IConfigHelper.TREE_GROWTH_MULTIPLIER, 0.5f, 0, 16f); - createConfig("Factor that multiplies the wood returned from harvesting a tree. You cheat.", - IConfigHelper.TREE_HARVEST_MULTIPLIER, 1f, 0f, 128f); - createConfig("Maximum harvesting hardness that can be calculated. Regardless of tree thickness.", - IConfigHelper.MAX_TREE_HARDNESS, 20f, 1f, 200f); - createConfig("A multiplier of tree hardness. Higher values make trees slower to chop, lower values makes them faster to chop.", - IConfigHelper.TREE_HARDNESS_MULTIPLIER, 1f, (1/128f), 32f); - createConfig("If enabled then sticks will be dropped for partial logs", - IConfigHelper.DROP_STICKS, true); - createConfig("Scales the growth for the environment. 0.5f is nominal. 0.0 trees only grow in their native biome. 1.0 trees grow anywhere like they are in their native biome", - IConfigHelper.SCALE_BIOME_GROWTH_RATE, 0.5f, 0.0f, 1.0f); - createConfig("The chance of a tree on depleted soil to die. 1/256(~0.004) averages to about 1 death every 16 minecraft days", - IConfigHelper.DISEASE_CHANCE, 0.0f, 0.0f, 1.0f); - createConfig("The maximum radius of a branch that is allowed to postRot away. 8 = Full block size. 24 = Full 3x3 thick size. Set to 0 to prevent rotting", - IConfigHelper.MAX_BRANCH_ROT_RADIUS, 5, 0, ThickBranchBlock.MAX_RADIUS_THICK); - createConfig("How much harder it is to destroy a rooty block compared to its non-rooty state", - IConfigHelper.ROOTY_BLOCK_HARDNESS_MULTIPLIER, 40f, 0f, 128f); - createConfig("Options for how oak trees generate in swamps. ROOTED: Swamp oak trees will generate on shallow water with mangrove-like roots. SUNK: Swamp oak trees will generate on shallow water one block under the surface. DISABLED: Swamp oaks will not generate on water.", - IConfigHelper.SWAMP_OAKS_IN_WATER, SwampSpecies.WaterSurfaceGenerationState.ROOTED.toString()); - createConfig("The amount of growth pulses to send when bone meal is applied to a tree. Warning: setting values higher than 64 is not recommended other than for testing purposes. ", - IConfigHelper.BONE_MEAL_GROWTH_PULSES, 1, 1, 512); - - configs.addSection("interaction"); - createConfig("If enabled all leaves will be passable. If the Passable Foliage mod is installed this config is overridden as true.", - IConfigHelper.IS_LEAVES_PASSABLE, false); - createConfig("If enabled player movement on leaves will not be enhanced.", - IConfigHelper.VANILLA_LEAVES_COLLISION, false); - createConfig("If enabled then thinner branches can be climbed", - IConfigHelper.ENABLE_BRANCH_CLIMBING, true); - createConfig("If enabled players receive reduced fall damage on leaves at the expense of the block(s) destruction", - IConfigHelper.ENABLE_CANOPY_CRASH, true); - createConfig("Damage dealt to the axe item when cutting a tree down. VANILLA: Standard 1 Damage. THICKNESS: By Branch/Trunk Thickness. VOLUME: By Tree Volume.", - IConfigHelper.AXE_DAMAGE_MODE, DynamicTrees.AxeDamage.THICKNESS.toString()); - createConfig("If enabled then trees will fall over when harvested", - IConfigHelper.ENABLE_FALLING_TREES, true); - createConfig("If enabled then trees will harm living entities when falling", - IConfigHelper.ENABLE_FALLING_TREE_DAMAGE, true); - createConfig("Multiplier for damage incurred by a falling tree", - IConfigHelper.FALLING_TREE_DAMAGE_MULTIPLIER, 1.0, 0.0, 100.0); - createConfig("If enabled the Dirt Bucket will place a dirt block on right-click", - IConfigHelper.DIRT_BUCKET_PLACES_DIRT, true); - createConfig("If enabled then improperly broken trees(not by an entity) will still drop wood.", - IConfigHelper.SLOPPY_BREAK_DROPS, false); - createConfig("The minimum radius a branch must have before its able to be stripped. 8 = Full block size. Set to 0 to disable stripping trees", - IConfigHelper.MIN_RADIUS_FOR_STRIP, 6, 0, 24); - createConfig("If enabled, stripping a branch will decrease its radius by one", - IConfigHelper.ENABLE_STRIP_RADIUS_REDUCTION, true); - createConfig("Sets the default for whether or not fruit growing from dynamic trees can be bone-mealed. Note that this is a default; it can be overridden by the individual fruit.", - IConfigHelper.CAN_BONE_MEAL_FRUIT, false); - createConfig("Sets the default for whether or not pods growing from dynamic trees can be bone-mealed. Note that this is a default; it can be overridden by the individual pod.", - IConfigHelper.CAN_BONE_MEAL_PODS, true); - createConfig("If enabled, dynamic sapling blocks will drop their seed when broken.", - IConfigHelper.DYNAMIC_SAPLING_DROPS, true); - - configs.addSection("vanilla"); - createConfig("Right clicking with a vanilla sapling places a dynamic sapling instead.", - IConfigHelper.REPLACE_VANILLA_SAPLINGS, false); - createConfig("Crimson Fungus and Warped Fungus that sprout from nylium will be dynamic instead.", - IConfigHelper.REPLACE_NYLIUM_FUNGI, true); - createConfig("If enabled, cancels the non-dynamic trees that spawn with vanilla villages.", - IConfigHelper.CANCEL_VANILLA_VILLAGE_TREES, true); - createConfig("The maximum number of leaves blocks that will fling particles when a falling tree crashes into the ground. Higher values might have a performance impact.", - IConfigHelper.MAX_FALLING_TREE_LEAVES_PARTICLES, 400, 0, 4096); - - configs.addSection("world"); - createConfig("Randomly generate podzol under select trees like spruce.", - IConfigHelper.GENERATE_PODZOL, true); - createConfig("World Generation produces Dynamic Trees instead of Vanilla trees.", - IConfigHelper.WORLD_GEN, true); - createConfig("Blacklist of dimension registry names for disabling Dynamic Tree worldgen.", - IConfigHelper.DIMENSION_BLACK_LIST, "[]"); - - configs.addSection("misc"); - createConfig("If enabled, dirt bucket recipes will be automatically generated.", - IConfigHelper.GENERATE_DIRT_BUCKET_RECIPES, true); - createConfig("If enabled, seeds for mega species can be crafted with four regular seeds.", - IConfigHelper.GENERATE_MEGA_SEED_RECIPE, false); - createConfig("The base potion the Biochar Base is brewed from. Minecraft potions use 'awkward'. If you change this, don't forget to update the patchouli manual page too.", - IConfigHelper.BIOCHAR_BREWING_BASE, "minecraft:thick"); - - configs.addSection("integration"); - createConfig("The mod ID of preferred season mod. If a season provider for this mod ID is present, it will be used for integration with seasons. Set this to \"!\" to disable integration or \"*\" to accept the any integration (the first available).", - IConfigHelper.PREFERRED_SEASON_MOD, SeasonCompatibilityHandler.ANY); - createConfig("If enabled, seed drop rates will be multiplied based on the current season (requires serene seasons).", - IConfigHelper.ENABLE_SEASONAL_SEED_DROP, true); - createConfig("If enabled, growth rates will be multiplied based on the current season (requires serene seasons).", - IConfigHelper.ENABLE_SEASONAL_GROWTH, true); - createConfig("If enabled, fruit production rates will be multiplied based on the current season (requires serene seasons).", - IConfigHelper.ENABLE_SEASONAL_SEED_FRUIT_PRODUCTION, true); - createConfig("The seasonal offset of the wet season relative to summer. Tropical and arid climates use wet/dry seasons instead of regular summer/fall/winter/spring seasons. Tree growth and fruit production usually peak during the wet season. If set to 0.0 the wet season happens at the same time as summer. The default of 1.5 means it happens between fall and winter.", - IConfigHelper.WET_SEASON_OFFSET, 1.5, 0.0, 4.0); - -// configs.addSection("visuals"); -// fancyThickRings = createConfig("Rings of thick trees are rendered using a texture created with an expanded tangram construction technique. Otherwise the ring texture is simply stretched", -// "fancyThickRings", true); -// CLIENT_BUILDER.pop(); - - configs.addSection("debug"); - createConfig("Enable to mark tree spawn locations with concrete circles.", - IConfigHelper.DEBUG, false); - } - - private static void createConfig(String comment, String id, T def){ - configs.addKeyValuePair(new Pair<>(id, def), comment, null); - } - private static void createConfig(String comment, String id, T def, T rangeMin, T rangeMax){ - configs.addKeyValuePair(new Pair<>(id, def), comment, new Pair<>(rangeMin, rangeMax)); - } - - public static Pair, ?> getDefaultValue (String id){ - return configs.getDefaultValues().get(id); - } - -} \ No newline at end of file diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/config/SimpleConfig.java b/fabric/src/main/java/com/dtteam/dynamictrees/config/SimpleConfig.java deleted file mode 100644 index 58c815023..000000000 --- a/fabric/src/main/java/com/dtteam/dynamictrees/config/SimpleConfig.java +++ /dev/null @@ -1,260 +0,0 @@ -package com.dtteam.dynamictrees.config; - -/* - * Copyright (c) 2021 magistermaks - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -import net.fabricmc.loader.api.FabricLoader; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import java.io.File; -import java.io.IOException; -import java.io.PrintWriter; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.HashMap; -import java.util.Objects; -import java.util.Scanner; - -public class SimpleConfig { - - private static final Logger LOGGER = LogManager.getLogger("SimpleConfig"); - private final HashMap config = new HashMap<>(); - private final ConfigRequest request; - private boolean broken = false; - - public interface DefaultConfig { - String get( String namespace ); - - static String empty( String namespace ) { - return ""; - } - } - - public static class ConfigRequest { - - private final File file; - private final String filename; - private DefaultConfig provider; - - private ConfigRequest(File file, String filename ) { - this.file = file; - this.filename = filename; - this.provider = DefaultConfig::empty; - } - - /** - * Sets the default config provider, used to generate the - * config if it's missing. - * - * @param provider default config provider - * @return current config request object - * @see DefaultConfig - */ - public ConfigRequest provider( DefaultConfig provider ) { - this.provider = provider; - return this; - } - - /** - * Loads the config from the filesystem. - * - * @return config object - * @see SimpleConfig - */ - public SimpleConfig request() { - return new SimpleConfig( this ); - } - - private String getConfig() { - return provider.get( filename ) + "\n"; - } - - } - - /** - * Creates new config request object, ideally `namespace` - * should be the name of the mod id of the requesting mod - * - * @param filename - name of the config file - * @return new config request object - */ - public static ConfigRequest of( String filename ) { - Path path = FabricLoader.getInstance().getConfigDir(); - return new ConfigRequest( path.resolve( filename + ".properties" ).toFile(), filename ); - } - - private void createConfig() throws IOException { - - // try creating missing files - request.file.getParentFile().mkdirs(); - Files.createFile( request.file.toPath() ); - - // write default config data - PrintWriter writer = new PrintWriter(request.file, StandardCharsets.UTF_8); - writer.write( request.getConfig() ); - writer.close(); - - } - - private void loadConfig() throws IOException { - Scanner reader = new Scanner( request.file ); - for( int line = 1; reader.hasNextLine(); line ++ ) { - parseConfigEntry( reader.nextLine(), line ); - } - } - - private void parseConfigEntry( String entry, int line ) { - if( !entry.isEmpty() && !(entry.trim().startsWith( "#" ) || (entry.trim().startsWith("[") && entry.trim().endsWith("]"))) ) { - String[] parts = entry.split("=", 2); - if( parts.length == 2 ) { - config.put( parts[0].trim(), parts[1].trim() ); - }else{ - throw new RuntimeException("Syntax error in config file on line " + line + "!"); - } - } - } - - private SimpleConfig( ConfigRequest request ) { - this.request = request; - String identifier = "Config '" + request.filename + "'"; - - if( !request.file.exists() ) { - LOGGER.info("{} is missing, generating default one...", identifier); - - try { - createConfig(); - } catch (IOException e) { - LOGGER.error("{} failed to generate!", identifier); - LOGGER.trace( e ); - broken = true; - } - } - - if( !broken ) { - try { - loadConfig(); - } catch (Exception e) { - LOGGER.error("{} failed to load!", identifier); - LOGGER.trace( e ); - broken = true; - } - } - - } - - /** - * Queries a value from config, returns `null` if the - * key does not exist. - * - * @return value corresponding to the given key - * @see SimpleConfig#getOrDefault - */ - @Deprecated - public String get( String key ) { - return config.get( key ); - } - - public boolean containsConfig (String key){ - return get(key) != null; - } - - /** - * Returns string value from config corresponding to the given - * key, or the default string if the key is missing. - * - * @return value corresponding to the given key, or the default value - */ - public String getOrDefault( String key, String def ) { - String val = get(key); - return val == null ? def : val; - } - - /** - * Returns integer value from config corresponding to the given - * key, or the default integer if the key is missing or invalid. - * - * @return value corresponding to the given key, or the default value - */ - public Integer getOrDefault( String key, Integer def ) { - try { - if (Objects.equals(get(key), "null")) return null; - return Integer.parseInt( get(key) ); - } catch (Exception e) { - return def; - } - } - - /** - * Returns boolean value from config corresponding to the given - * key, or the default boolean if the key is missing. - * - * @return value corresponding to the given key, or the default value - */ - public Boolean getOrDefault( String key, Boolean def ) { - if (Objects.equals(get(key), "null")) return null; - String val = get(key); - if( val != null ) { - return val.equalsIgnoreCase("true"); - } - - return def; - } - - /** - * Returns double value from config corresponding to the given - * key, or the default string if the key is missing or invalid. - * - * @return value corresponding to the given key, or the default value - */ - public Double getOrDefault( String key, Double def ) { - try { - if (Objects.equals(get(key), "null")) return null; - return Double.parseDouble( get(key) ); - } catch (Exception e) { - return def; - } - } - - /** - * If any error occurred during loading or reading from the config - * a 'broken' flag is set, indicating that the config's state - * is undefined and should be discarded using `delete()` - * - * @return the 'broken' flag of the configuration - */ - public boolean isBroken() { - return broken; - } - - /** - * deletes the config file from the filesystem - * - * @return true if the operation was successful - */ - public boolean delete() { - LOGGER.warn( "Config '" + request.filename + "' was removed from existence! Restart the game to regenerate it." ); - return request.file.delete(); - } - -} \ No newline at end of file diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/VanillaSaplingEventHandler.java b/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/VanillaSaplingEventHandler.java index 5725fe8fc..cd2404009 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/VanillaSaplingEventHandler.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/event/handler/VanillaSaplingEventHandler.java @@ -1,8 +1,7 @@ package com.dtteam.dynamictrees.event.handler; import com.dtteam.dynamictrees.block.sapling.DynamicSaplingBlock; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.tree.species.Species; import net.fabricmc.fabric.api.event.player.UseBlockCallback; import net.minecraft.core.BlockPos; @@ -24,7 +23,7 @@ public static void register() { } public static void updateEnabled() { - isEnabled = Services.CONFIG.getBoolConfig(IConfigHelper.REPLACE_VANILLA_SAPLINGS); + isEnabled = DTConfigs.COMMON.replaceVanillaSaplings.get(); } private static InteractionResult onUseBlock(Player player, Level level, InteractionHand hand, BlockHitResult hitResult) { diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinChunkSerializer.java b/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinChunkSerializer.java index 29cad3061..1edb5147f 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinChunkSerializer.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinChunkSerializer.java @@ -1,8 +1,7 @@ package com.dtteam.dynamictrees.mixin; import com.dtteam.dynamictrees.api.worldgen.LevelContext; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.systems.poissondisc.UniversalPoissonDiscProvider; import com.dtteam.dynamictrees.worldgen.feature.DynamicTreeFeature; import net.minecraft.nbt.CompoundTag; @@ -24,7 +23,7 @@ public class MixinChunkSerializer { @Inject(at = @At("HEAD"), method = "read (Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/entity/ai/village/poi/PoiManager;Lnet/minecraft/world/level/chunk/storage/RegionStorageInfo;Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/nbt/CompoundTag;)Lnet/minecraft/world/level/chunk/ProtoChunk;") private static void read(ServerLevel level, PoiManager poiManager, RegionStorageInfo regionStorageInfo, ChunkPos pos, CompoundTag tag, CallbackInfoReturnable cir) { - if (!Services.CONFIG.getBoolConfig(IConfigHelper.WORLD_GEN)) return; + if (!DTConfigs.SERVER.worldGen.get()) return; final byte[] circleData = tag.getByteArray(UniversalPoissonDiscProvider.CIRCLE_DATA_ID); final UniversalPoissonDiscProvider discProvider = DynamicTreeFeature.DISC_PROVIDER; @@ -34,7 +33,7 @@ private static void read(ServerLevel level, PoiManager poiManager, RegionStorage @Inject(at = @At("RETURN"), method = "write (Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/level/chunk/ChunkAccess;)Lnet/minecraft/nbt/CompoundTag;") private static void write(ServerLevel level, ChunkAccess chunk, CallbackInfoReturnable cir) { - if (!Services.CONFIG.getBoolConfig(IConfigHelper.WORLD_GEN)) return; + if (!DTConfigs.SERVER.worldGen.get()) return; final LevelContext levelContext = LevelContext.create(level); final UniversalPoissonDiscProvider discProvider = DynamicTreeFeature.DISC_PROVIDER; diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinSaplingBlock.java b/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinSaplingBlock.java index 06fab9999..6224068c0 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinSaplingBlock.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/mixin/MixinSaplingBlock.java @@ -1,8 +1,7 @@ package com.dtteam.dynamictrees.mixin; import com.dtteam.dynamictrees.block.sapling.DynamicSaplingBlock; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.tree.species.Species; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; @@ -20,7 +19,7 @@ public class MixinSaplingBlock { @Inject(method = "advanceTree", at = @At("HEAD"), cancellable = true) private void onAdvanceTree(ServerLevel level, BlockPos pos, BlockState state, RandomSource random, CallbackInfo ci) { - if (!Services.CONFIG.getBoolConfig(IConfigHelper.REPLACE_VANILLA_SAPLINGS)) { + if (!DTConfigs.COMMON.replaceVanillaSaplings.get()) { return; } diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricConfigHelper.java b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricConfigHelper.java index ea2be94ef..3fba475d4 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricConfigHelper.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/platform/FabricConfigHelper.java @@ -11,83 +11,48 @@ public class FabricConfigHelper implements IConfigHelper { - private T getConfig(String config, Class tClass){ - if (!DTConfigs.CONFIG.containsConfig(config)){ - DynamicTrees.LOG.error("Failed to get configuration \"{}\" of {} as it does not exist.", config, tClass); - } - Pair, ?> def = DTConfigs.getDefaultValue(config); - if (!tClass.equals(def.getFirst())) { - DynamicTrees.LOG.error("Failed to get configuration \"{}\" of {} as it is of {} instead.", config, tClass, def.getFirst()); - return null; - } - return tClass.cast(def.getSecond()); + private T getConfig(String config, Class tClass) { + return null; } @Override public Boolean getBoolConfig(String config){ - return DTConfigs.CONFIG.getOrDefault(config, getConfig(config, Boolean.class)); + return getConfig(config, Boolean.class); } @Override public Integer getIntConfig(String config){ - return DTConfigs.CONFIG.getOrDefault(config, getConfig(config, Integer.class)); + return getConfig(config, Integer.class); } @Override public Double getDoubleConfig(String config){ - if (!DTConfigs.CONFIG.containsConfig(config)){ - DynamicTrees.LOG.error("Failed to get configuration \"{}\" of {} as it does not exist.", config, Double.class); - return null; - } - Pair, ?> def = DTConfigs.getDefaultValue(config); - Double defaultVal; - if (def.getFirst().equals(Float.class)) { - defaultVal = ((Float) def.getSecond()).doubleValue(); - } else if (def.getFirst().equals(Double.class)) { - defaultVal = (Double) def.getSecond(); - } else { - DynamicTrees.LOG.error("Failed to get configuration \"{}\" of {} as it is of {} instead.", config, Double.class, def.getFirst()); - return null; - } - return DTConfigs.CONFIG.getOrDefault(config, defaultVal); + return getConfig(config, Double.class); } @Override public String getStringConfig(String config){ - return DTConfigs.CONFIG.getOrDefault(config, getConfig(config, String.class)); + return getConfig(config, String.class); } - @Override public > T getEnumConfig(String config, Class tClass) { - String value = getStringConfig(config); - return Enum.valueOf(tClass, value.toUpperCase(Locale.ENGLISH)); + return getConfig(config, tClass); } - + @SuppressWarnings("unchecked") @Override public List getStringListConfig(String config){ - String array = getStringConfig(config); - if (array == null) return new ArrayList<>(); - array = array.trim().substring(1, array.length()-1); //remove [ and ] - String[] values = array.split(","); - List stringList = new ArrayList<>(); - for (String val : values) { //remove " and " - if (val.isEmpty()) continue; - val = val.trim(); - if (val.charAt(0) == '\"') val = val.substring(1, val.length()-1).trim(); - stringList.add(val); - } - return stringList; + return getConfig(config, List.class); } @Override public boolean isServerConfigLoaded() { - return DTConfigs.isLoaded; + return DTConfigs.SERVER_CONFIG.isLoaded(); } @Override public boolean isCommonConfigLoaded() { - return DTConfigs.isLoaded; + return DTConfigs.COMMON_CONFIG.isLoaded(); } @Override public boolean isClientConfigLoaded() { - return DTConfigs.isLoaded; + return DTConfigs.CLIENT_CONFIG.isLoaded(); } } \ No newline at end of file diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/recipe/DendroPotionRecipeHandler.java b/fabric/src/main/java/com/dtteam/dynamictrees/recipe/DendroPotionRecipeHandler.java index 46c17c2ce..0e14f793e 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/recipe/DendroPotionRecipeHandler.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/recipe/DendroPotionRecipeHandler.java @@ -1,8 +1,7 @@ package com.dtteam.dynamictrees.recipe; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.item.DendroPotion; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; import com.dtteam.dynamictrees.registry.DTRegistries; import net.minecraft.core.Holder; import net.minecraft.core.component.DataComponents; @@ -35,12 +34,10 @@ public static boolean isIngredient(ItemStack stack){ } public static List getAllDendroRecipes() { - //If they have already been processed then don't process them again if (!brewingRecipes.isEmpty()) return brewingRecipes; - //Biochar potion ItemStack biocharIngredient = new ItemStack(Items.CHARCOAL); - final ItemStack baseStack = setPotion(new ItemStack(Items.POTION), Services.CONFIG.getStringConfig(IConfigHelper.BIOCHAR_BREWING_BASE)); + final ItemStack baseStack = setPotion(new ItemStack(Items.POTION), DTConfigs.COMMON.biocharBrewingBase.get()); brewingRecipes.add(getRecipe(baseStack, biocharIngredient, getPotionStack(DendroPotion.DendroPotionType.BIOCHAR))); ingredients.add(biocharIngredient); diff --git a/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/FabricBiomeModifications.java b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/FabricBiomeModifications.java index c55e6742f..e6a943bb7 100644 --- a/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/FabricBiomeModifications.java +++ b/fabric/src/main/java/com/dtteam/dynamictrees/worldgen/FabricBiomeModifications.java @@ -3,8 +3,7 @@ import com.dtteam.dynamictrees.DynamicTrees; import com.dtteam.dynamictrees.api.worldgen.BiomePropertySelectors; import com.dtteam.dynamictrees.api.worldgen.FeatureCanceller; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.registry.DTRegistries; import com.dtteam.dynamictrees.worldgen.featurecancellation.FeatureCancellationRegistry; import net.fabricmc.fabric.api.biome.v1.BiomeModificationContext; @@ -36,16 +35,20 @@ public class FabricBiomeModifications { public static void register() { BiomeModifications.create(REMOVE_TREES_ID) .add(ModificationPhase.REMOVALS, BiomeSelectors.all(), (selectionContext, modificationContext) -> { - if (!Services.CONFIG.getBoolConfig(IConfigHelper.WORLD_GEN)) { - return; + if(DTConfigs.SERVER_CONFIG.isLoaded()) { + if (!DTConfigs.SERVER.worldGen.get()) { + return; + } } removeVanillaTrees(selectionContext, modificationContext); }); BiomeModifications.create(ADD_TREES_ID) .add(ModificationPhase.ADDITIONS, BiomeSelectors.all(), (selectionContext, modificationContext) -> { - if (!Services.CONFIG.getBoolConfig(IConfigHelper.WORLD_GEN)) { - return; + if(DTConfigs.SERVER_CONFIG.isLoaded()) { + if (!DTConfigs.SERVER.worldGen.get()) { + return; + } } addDynamicTrees(modificationContext); }); diff --git a/gradle.properties b/gradle.properties index 31ad06d3d..9cea40597 100644 --- a/gradle.properties +++ b/gradle.properties @@ -25,7 +25,7 @@ parchment_version=2024.11.10 java_version=21 # Fabric -fabric_version=0.109.0+1.21.1 +fabric_version=0.116.8+1.21.1 fabric_loader_version=0.18.4 # Forge // Unused @@ -39,4 +39,4 @@ neoforge_loader_version_range=[4,) # Gradle org.gradle.jvmargs=-Xmx3G org.gradle.daemon=false -regutils_version=1.21.0-0.2.2 +forge_config_api_port_version=21.1.6 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 94113f200..ca025c83a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/neoforge/src/main/java/com/dtteam/dynamictrees/DynamicTreesNeoForge.java b/neoforge/src/main/java/com/dtteam/dynamictrees/DynamicTreesNeoForge.java index e96883692..bdf491a32 100644 --- a/neoforge/src/main/java/com/dtteam/dynamictrees/DynamicTreesNeoForge.java +++ b/neoforge/src/main/java/com/dtteam/dynamictrees/DynamicTreesNeoForge.java @@ -4,7 +4,7 @@ import com.dtteam.dynamictrees.block.leaves.LeavesProperties; import com.dtteam.dynamictrees.block.soil.SoilProperties; import com.dtteam.dynamictrees.client.BlockColorMultipliers; -import com.dtteam.dynamictrees.config.DTConfigs; +import com.dtteam.dynamictrees.config.*; import com.dtteam.dynamictrees.data.GatherDataHelper; import com.dtteam.dynamictrees.data.generator.DTExtraLangGenerator; import com.dtteam.dynamictrees.data.generator.DataGenerators; diff --git a/neoforge/src/main/java/com/dtteam/dynamictrees/config/DTConfigEvents.java b/neoforge/src/main/java/com/dtteam/dynamictrees/config/DTConfigEvents.java new file mode 100644 index 000000000..c1e418b5a --- /dev/null +++ b/neoforge/src/main/java/com/dtteam/dynamictrees/config/DTConfigEvents.java @@ -0,0 +1,25 @@ +package com.dtteam.dynamictrees.config; + +import com.dtteam.dynamictrees.DynamicTrees; +import com.dtteam.dynamictrees.event.handler.OptionalHandlers; +import com.dtteam.dynamictrees.systems.season.SeasonCompatibilityHandler; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.fml.event.config.ModConfigEvent; + +@EventBusSubscriber(modid = DynamicTrees.MOD_ID, bus = EventBusSubscriber.Bus.MOD) +public class DTConfigEvents { + + @SubscribeEvent + public static void onLoad(final ModConfigEvent.Loading event) { + OptionalHandlers.configReload(); + SeasonCompatibilityHandler.reloadSeasonManager(); + } + + @SubscribeEvent + public static void onReload(final ModConfigEvent.Reloading event) { + OptionalHandlers.configReload(); + SeasonCompatibilityHandler.reloadSeasonManager(); + } + +} diff --git a/neoforge/src/main/java/com/dtteam/dynamictrees/config/DTConfigs.java b/neoforge/src/main/java/com/dtteam/dynamictrees/config/DTConfigs.java deleted file mode 100644 index b7691045d..000000000 --- a/neoforge/src/main/java/com/dtteam/dynamictrees/config/DTConfigs.java +++ /dev/null @@ -1,192 +0,0 @@ -package com.dtteam.dynamictrees.config; - -import com.dtteam.dynamictrees.DynamicTrees; -import com.dtteam.dynamictrees.block.branch.ThickBranchBlock; -import com.dtteam.dynamictrees.event.handler.OptionalHandlers; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; -import com.dtteam.dynamictrees.systems.season.SeasonCompatibilityHandler; -import com.dtteam.dynamictrees.tree.species.SwampSpecies; -import net.neoforged.bus.api.SubscribeEvent; -import net.neoforged.fml.common.EventBusSubscriber; -import net.neoforged.fml.event.config.ModConfigEvent; -import net.neoforged.fml.loading.FMLPaths; -import net.neoforged.neoforge.common.ModConfigSpec; - -import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -@EventBusSubscriber(modid = DynamicTrees.MOD_ID, bus = EventBusSubscriber.Bus.MOD) -public class DTConfigs { - - public static final File CONFIG_DIRECTORY; - - public static final Map> CONFIGS = new HashMap<>(); - - public static final ModConfigSpec SERVER_CONFIG; - public static final ModConfigSpec COMMON_CONFIG; - public static final ModConfigSpec CLIENT_CONFIG; - - private static > T registerConfig (T config){ - CONFIGS.put(config.getPath().getLast(), config); - return config; - } - - static { - CONFIG_DIRECTORY = new File(FMLPaths.CONFIGDIR.get().toUri()); - - final ModConfigSpec.Builder SERVER_BUILDER = new ModConfigSpec.Builder(); - final ModConfigSpec.Builder COMMON_BUILDER = new ModConfigSpec.Builder(); - final ModConfigSpec.Builder CLIENT_BUILDER = new ModConfigSpec.Builder(); - - SERVER_BUILDER.comment("Seed Settings").push("seeds"); - registerConfig(SERVER_BUILDER.comment("The rate at which seeds drop from leaves."). - defineInRange(IConfigHelper.LEAVES_SEED_DROP_RATE, 1.0, 0.0, 64.0)); - registerConfig(SERVER_BUILDER.comment("The minimum chance for seed dropping from leaves when a seasonal mod is installed. 0 = during the off season seeds never drop from leaves, 1 = seeds will drop at maximum rate during the entire year. Can be fractional."). - defineInRange(IConfigHelper.MIN_SEASONAL_LEAVES_SEED_DROP_RATE, 0.15, 0.0, 1.0)); - registerConfig(SERVER_BUILDER.comment("The rate at which seeds voluntarily drop from branches"). - defineInRange(IConfigHelper.VOLUNTARY_SEED_DROP_RATE, 0.01, 0.0, 1.0)); - registerConfig(SERVER_BUILDER.comment("The minimum chance for seed dropping voluntarily when a seasonal mod is installed. 0 = during the off season seeds never drop voluntarily, 1 = seeds will drop at maximum rate during the entire year. Can be fractional."). - defineInRange(IConfigHelper.MIN_SEASONAL_VOLUNTARY_SEED_DROP_RATE, 0.0, 0.0, 1.0)); - registerConfig(SERVER_BUILDER.comment("The rate at which seeds voluntarily plant themselves in their ideal biomes"). - defineInRange(IConfigHelper.SEED_PLANT_RATE, 1f / 6f, 0.0, 1.0)); - registerConfig(SERVER_BUILDER.comment("Ticks before a seed in the world attempts to plant itself or despawn. 1200 = 1 minute"). - defineInRange(IConfigHelper.SEED_TIME_TO_LIVE, 1200, 0, 6000)); - registerConfig(SERVER_BUILDER.comment("If enabled then seeds will only voluntarily plant themselves in forest-like biomes."). - define(IConfigHelper.SEED_ONLY_FOREST, true)); - registerConfig(SERVER_BUILDER.comment("The minimum forestness that non-forest-like biomes can have. 0 = is not at all a forest, 1 = may as well be a forest. Can be fractional."). - defineInRange(IConfigHelper.SEED_MIN_FORESTNESS, 0.0, 0.0, 1.0)); - registerConfig(SERVER_BUILDER.comment("If enabled, fruit and pod production will be affected by the current biome's climate."). - define(IConfigHelper.CLIMATE_AFFECTS_FRUITS_AND_PODS, true)); - SERVER_BUILDER.pop(); - - SERVER_BUILDER.comment("Tree Settings").push("trees"); - registerConfig(SERVER_BUILDER.comment("Factor that multiplies the rate at which trees grow. Use at own risk"). - defineInRange(IConfigHelper.TREE_GROWTH_MULTIPLIER, 0.5f, 0, 16f)); - registerConfig(SERVER_BUILDER.comment("Factor that multiplies the wood returned from harvesting a tree. You cheat."). - defineInRange(IConfigHelper.TREE_HARVEST_MULTIPLIER, 1f, 0f, 128f)); - registerConfig(SERVER_BUILDER.comment("Maximum harvesting hardness that can be calculated. Regardless of tree thickness."). - defineInRange(IConfigHelper.MAX_TREE_HARDNESS, 20f, 1f, 200f)); - registerConfig(SERVER_BUILDER.comment("A multiplier of tree hardness. Higher values make trees slower to chop, lower values makes them faster to chop."). - defineInRange(IConfigHelper.TREE_HARDNESS_MULTIPLIER, 1, (1/128f), 32f)); - registerConfig(SERVER_BUILDER.comment("If enabled then sticks will be dropped for partial logs"). - define(IConfigHelper.DROP_STICKS, true)); - registerConfig(SERVER_BUILDER.comment("Scales the growth for the environment. 0.5f is nominal. 0.0 trees only grow in their native biome. 1.0 trees grow anywhere like they are in their native biome"). - defineInRange(IConfigHelper.SCALE_BIOME_GROWTH_RATE, 0.5f, 0.0f, 1.0f)); - registerConfig(SERVER_BUILDER.comment("The chance of a tree on depleted soil to die. 1/256(~0.004) averages to about 1 death every 16 minecraft days"). - defineInRange(IConfigHelper.DISEASE_CHANCE, 0.0f, 0.0f, 1.0f)); - registerConfig(SERVER_BUILDER.comment("The maximum radius of a branch that is allowed to postRot away. 8 = Full block size. 24 = Full 3x3 thick size. Set to 0 to prevent rotting"). - defineInRange(IConfigHelper.MAX_BRANCH_ROT_RADIUS, 7, 0, ThickBranchBlock.MAX_RADIUS_THICK)); - registerConfig(SERVER_BUILDER.comment("How much harder it is to destroy a rooty block compared to its non-rooty state"). - defineInRange(IConfigHelper.ROOTY_BLOCK_HARDNESS_MULTIPLIER, 40f, 0f, 128f)); - registerConfig(SERVER_BUILDER.comment("Options for how oak trees generate in swamps. ROOTED: Swamp oak trees will generate on shallow water with mangrove-like roots. SUNK: Swamp oak trees will generate on shallow water one block under the surface. DISABLED: Swamp oaks will not generate on water."). - defineEnum(IConfigHelper.SWAMP_OAKS_IN_WATER, SwampSpecies.WaterSurfaceGenerationState.ROOTED)); - registerConfig(SERVER_BUILDER.comment("The amount of growth pulses to send when bone meal is applied to a tree. Warning: setting values higher than 64 is not recommended other than for testing purposes. "). - defineInRange(IConfigHelper.BONE_MEAL_GROWTH_PULSES, 1, 1, 512)); - SERVER_BUILDER.pop(); - - SERVER_BUILDER.comment("Interaction Settings").push("interaction"); - registerConfig(SERVER_BUILDER.comment("If enabled all leaves will be passable. If the Passable Foliage mod is installed this config is overridden"). - define(IConfigHelper.IS_LEAVES_PASSABLE, false)); - registerConfig(SERVER_BUILDER.comment("If enabled player movement on leaves will not be enhanced"). - define(IConfigHelper.VANILLA_LEAVES_COLLISION, false)); - registerConfig(SERVER_BUILDER.comment("If enabled then thinner branches can be climbed"). - define(IConfigHelper.ENABLE_BRANCH_CLIMBING, true)); - registerConfig(SERVER_BUILDER.comment("If enabled players receive reduced fall damage on leaves at the expense of the block(s) destruction"). - define(IConfigHelper.ENABLE_CANOPY_CRASH, true)); - registerConfig(SERVER_BUILDER.comment("Damage dealt to the axe item when cutting a tree down. VANILLA: Standard 1 Damage. THICKNESS: By Branch/Trunk Thickness. VOLUME: By Tree Volume."). - defineEnum(IConfigHelper.AXE_DAMAGE_MODE, DynamicTrees.AxeDamage.THICKNESS)); - registerConfig(SERVER_BUILDER.comment("If enabled then trees will fall over when harvested"). - define(IConfigHelper.ENABLE_FALLING_TREES, true)); - registerConfig(SERVER_BUILDER.comment("If enabled then trees will harm living entities when falling"). - define(IConfigHelper.ENABLE_FALLING_TREE_DAMAGE, true)); - registerConfig(SERVER_BUILDER.comment("Multiplier for damage incurred by a falling tree"). - defineInRange(IConfigHelper.FALLING_TREE_DAMAGE_MULTIPLIER, 1.0, 0.0, 100.0)); - registerConfig(SERVER_BUILDER.comment("If enabled the Dirt Bucket will place a dirt block on right-click"). - define(IConfigHelper.DIRT_BUCKET_PLACES_DIRT, true)); - registerConfig(SERVER_BUILDER.comment("If enabled then improperly broken trees(not by an entity) will still drop wood."). - define(IConfigHelper.SLOPPY_BREAK_DROPS, false)); - registerConfig(SERVER_BUILDER.comment("The minimum radius a branch must have before its able to be stripped. 8 = Full block size. Set to 0 to disable stripping trees"). - defineInRange(IConfigHelper.MIN_RADIUS_FOR_STRIP, 6, 0, 24)); - registerConfig(SERVER_BUILDER.comment("If enabled, stripping a branch will decrease its radius by one"). - define(IConfigHelper.ENABLE_STRIP_RADIUS_REDUCTION, true)); - registerConfig(SERVER_BUILDER.comment("Sets the default for whether or not fruit growing from dynamic trees can be bone-mealed. Note that this is a default; it can be overridden by the individual fruit."). - define(IConfigHelper.CAN_BONE_MEAL_FRUIT, false)); - registerConfig(SERVER_BUILDER.comment("Sets the default for whether or not pods growing from dynamic trees can be bone-mealed. Note that this is a default; it can be overridden by the individual pod."). - define(IConfigHelper.CAN_BONE_MEAL_PODS, true)); - registerConfig(SERVER_BUILDER.comment("If enabled, dynamic sapling blocks will drop their seed when broken."). - define(IConfigHelper.DYNAMIC_SAPLING_DROPS, true)); - SERVER_BUILDER.pop(); - - COMMON_BUILDER.comment("Vanilla Trees Settings").push("vanilla"); - registerConfig(COMMON_BUILDER.comment("Right clicking with a vanilla sapling places a dynamic sapling instead."). - define(IConfigHelper.REPLACE_VANILLA_SAPLINGS, false)); - registerConfig(COMMON_BUILDER.comment("Crimson Fungus and Warped Fungus that sprout from nylium will be dynamic instead."). - define(IConfigHelper.REPLACE_NYLIUM_FUNGI, true)); - registerConfig(COMMON_BUILDER.comment("If enabled, cancels the non-dynamic trees that spawn with vanilla villages."). - define(IConfigHelper.CANCEL_VANILLA_VILLAGE_TREES, true)); - registerConfig(COMMON_BUILDER.comment("The maximum number of leaves blocks that will fling particles when a falling tree crashes into the ground. Higher values might have a performance impact."). - defineInRange(IConfigHelper.MAX_FALLING_TREE_LEAVES_PARTICLES, 400, 0, 4096)); - COMMON_BUILDER.pop(); - - SERVER_BUILDER.comment("World Generation Settings").push("world"); - registerConfig(SERVER_BUILDER.comment("Randomly generate podzol under select trees like spruce."). - define(IConfigHelper.GENERATE_PODZOL, true)); - registerConfig(SERVER_BUILDER.comment("World Generation produces Dynamic Trees instead of Vanilla trees."). - define(IConfigHelper.WORLD_GEN, true)); - registerConfig(SERVER_BUILDER.comment("Blacklist of dimension registry names for disabling Dynamic Tree worldgen"). - define(IConfigHelper.DIMENSION_BLACK_LIST, new ArrayList<>())); - SERVER_BUILDER.pop(); - - COMMON_BUILDER.comment("Miscellaneous Settings").push("misc"); - registerConfig(COMMON_BUILDER.comment("If enabled, dirt bucket recipes will be automatically generated.") - .define(IConfigHelper.GENERATE_DIRT_BUCKET_RECIPES, true)); - registerConfig(COMMON_BUILDER.comment("If enabled, seeds for mega species can be crafted with four regular seeds.") - .define(IConfigHelper.GENERATE_MEGA_SEED_RECIPE, false)); - registerConfig(COMMON_BUILDER.comment("The base potion the Biochar Base is brewed from. Minecraft potions use 'awkward'. If you change this, don't forget to update the patchouli manual page too.") - .define(IConfigHelper.BIOCHAR_BREWING_BASE, "minecraft:thick")); - COMMON_BUILDER.pop(); - - COMMON_BUILDER.comment("Mod Integration Settings").push("integration"); - registerConfig(COMMON_BUILDER.comment("The mod ID of preferred season mod. If a season provider for this mod ID is present, it will be used for integration with seasons. Set this to \"!\" to disable integration or \"*\" to accept the any integration (the first available).") - .define(IConfigHelper.PREFERRED_SEASON_MOD, SeasonCompatibilityHandler.ANY)); - registerConfig(COMMON_BUILDER.comment("If enabled, seed drop rates will be multiplied based on the current season (requires serene seasons)."). - define(IConfigHelper.ENABLE_SEASONAL_SEED_DROP, true)); - registerConfig(COMMON_BUILDER.comment("If enabled, growth rates will be multiplied based on the current season (requires serene seasons)."). - define(IConfigHelper.ENABLE_SEASONAL_GROWTH, true)); - registerConfig(COMMON_BUILDER.comment("If enabled, fruit production rates will be multiplied based on the current season (requires serene seasons)."). - define(IConfigHelper.ENABLE_SEASONAL_SEED_FRUIT_PRODUCTION, true)); - registerConfig(COMMON_BUILDER.comment("The seasonal offset of the wet season relative to summer. Tropical and arid climates use wet/dry seasons instead of regular summer/fall/winter/spring seasons. Tree growth and fruit production usually peak during the wet season. If set to 0.0 the wet season happens at the same time as summer. The default of 2.5 means it happens between fall and winter."). - defineInRange(IConfigHelper.WET_SEASON_OFFSET, 2.5, 0.0, 4.0)); - - COMMON_BUILDER.pop(); - -// CLIENT_BUILDER.comment("Visual Settings").push("visuals"); -// fancyThickRings = CLIENT_BUILDER.comment("Rings of thick trees are rendered using a texture created with an expanded tangram construction technique. Otherwise the ring texture is simply stretched"). -// define("fancyThickRings", true); -// CLIENT_BUILDER.pop(); - - SERVER_BUILDER.comment("Debug Settings").push("debug"); - registerConfig(SERVER_BUILDER.comment("Enable to mark tree spawn locations with concrete circles."). - define(IConfigHelper.DEBUG, false)); - SERVER_BUILDER.pop(); - - SERVER_CONFIG = SERVER_BUILDER.build(); - COMMON_CONFIG = COMMON_BUILDER.build(); - CLIENT_CONFIG = CLIENT_BUILDER.build(); - } - - @SubscribeEvent - public static void onLoad(final ModConfigEvent.Loading event) { - OptionalHandlers.configReload(); - SeasonCompatibilityHandler.reloadSeasonManager(); - } - - @SubscribeEvent - public static void onReload(final ModConfigEvent.Reloading event) { - OptionalHandlers.configReload(); - SeasonCompatibilityHandler.reloadSeasonManager(); - } - -} diff --git a/neoforge/src/main/java/com/dtteam/dynamictrees/event/handler/CommonGameEventHandler.java b/neoforge/src/main/java/com/dtteam/dynamictrees/event/handler/CommonGameEventHandler.java index 4f039cfea..1d6857565 100644 --- a/neoforge/src/main/java/com/dtteam/dynamictrees/event/handler/CommonGameEventHandler.java +++ b/neoforge/src/main/java/com/dtteam/dynamictrees/event/handler/CommonGameEventHandler.java @@ -3,8 +3,7 @@ import com.dtteam.dynamictrees.DynamicTrees; import com.dtteam.dynamictrees.api.worldgen.LevelContext; import com.dtteam.dynamictrees.command.DTCommand; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.recipe.DendroPotionRecipeHandler; import com.dtteam.dynamictrees.systems.FutureBreak; import com.dtteam.dynamictrees.systems.poissondisc.UniversalPoissonDiscProvider; @@ -65,7 +64,7 @@ public static void onLevelUnload(LevelEvent.Unload event) { @SubscribeEvent public static void onChunkDataLoad(ChunkDataEvent.Load event) { - if (!Services.CONFIG.getBoolConfig(IConfigHelper.WORLD_GEN)) return; + if (!DTConfigs.SERVER.worldGen.get()) return; final LevelAccessor level = event.getLevel(); @@ -82,7 +81,7 @@ public static void onChunkDataLoad(ChunkDataEvent.Load event) { @SubscribeEvent public static void onChunkDataSave(ChunkDataEvent.Save event) { - if (!Services.CONFIG.getBoolConfig(IConfigHelper.WORLD_GEN)) return; + if (!DTConfigs.SERVER.worldGen.get()) return; final LevelContext levelContext = LevelContext.create(event.getLevel()); final UniversalPoissonDiscProvider discProvider = DynamicTreeFeature.DISC_PROVIDER; diff --git a/neoforge/src/main/java/com/dtteam/dynamictrees/event/handler/OptionalHandlers.java b/neoforge/src/main/java/com/dtteam/dynamictrees/event/handler/OptionalHandlers.java index e37ac213b..f3f3ec142 100644 --- a/neoforge/src/main/java/com/dtteam/dynamictrees/event/handler/OptionalHandlers.java +++ b/neoforge/src/main/java/com/dtteam/dynamictrees/event/handler/OptionalHandlers.java @@ -3,7 +3,6 @@ import com.dtteam.dynamictrees.DynamicTrees; import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; import net.neoforged.bus.api.IEventBus; import net.neoforged.neoforge.common.NeoForge; @@ -31,10 +30,10 @@ public static void registerHandlers() { /** * Registers or unregisters event handlers based on config changes. Called when the config is loaded or reloaded in - * {@link DTConfigs}. + * {@link com.dtteam.dynamictrees.config.DTConfigEvents}. */ public static void configReload() { - registerOrUnregister(VANILLA_SAPLING_EVENT_HANDLER, Services.CONFIG.getBoolConfig(IConfigHelper.REPLACE_VANILLA_SAPLINGS)); + registerOrUnregister(VANILLA_SAPLING_EVENT_HANDLER, DTConfigs.COMMON.replaceVanillaSaplings.get()); } /** diff --git a/neoforge/src/main/java/com/dtteam/dynamictrees/platform/NeoForgeConfigHelper.java b/neoforge/src/main/java/com/dtteam/dynamictrees/platform/NeoForgeConfigHelper.java index d152ad771..9a3b9eaca 100644 --- a/neoforge/src/main/java/com/dtteam/dynamictrees/platform/NeoForgeConfigHelper.java +++ b/neoforge/src/main/java/com/dtteam/dynamictrees/platform/NeoForgeConfigHelper.java @@ -1,7 +1,7 @@ package com.dtteam.dynamictrees.platform; import com.dtteam.dynamictrees.DynamicTrees; -import com.dtteam.dynamictrees.config.DTConfigs; +import com.dtteam.dynamictrees.config.*; import com.dtteam.dynamictrees.platform.services.IConfigHelper; import java.util.List; diff --git a/neoforge/src/main/java/com/dtteam/dynamictrees/recipe/DendroPotionRecipeHandler.java b/neoforge/src/main/java/com/dtteam/dynamictrees/recipe/DendroPotionRecipeHandler.java index 6c3f03af5..cab0ef2a8 100644 --- a/neoforge/src/main/java/com/dtteam/dynamictrees/recipe/DendroPotionRecipeHandler.java +++ b/neoforge/src/main/java/com/dtteam/dynamictrees/recipe/DendroPotionRecipeHandler.java @@ -1,8 +1,7 @@ package com.dtteam.dynamictrees.recipe; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.item.DendroPotion; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; import com.dtteam.dynamictrees.registry.DTRegistries; import net.minecraft.core.Holder; import net.minecraft.core.component.DataComponents; @@ -24,11 +23,9 @@ public class DendroPotionRecipeHandler { private static final List brewingRecipes = new ArrayList<>(); public static List getAllDendroRecipes() { - //If they have already been processed then don't process them again if (!brewingRecipes.isEmpty()) return brewingRecipes; - //Biochar potion - final ItemStack baseStack = setPotion(new ItemStack(Items.POTION), Services.CONFIG.getStringConfig(IConfigHelper.BIOCHAR_BREWING_BASE)); + final ItemStack baseStack = setPotion(new ItemStack(Items.POTION), DTConfigs.COMMON.biocharBrewingBase.get()); brewingRecipes.add(getRecipe(baseStack, new ItemStack(Items.CHARCOAL), getPotionStack(DendroPotion.DendroPotionType.BIOCHAR))); //Regular potions diff --git a/neoforge/src/main/java/com/dtteam/dynamictrees/worldgen/biomemodifier/AddDynamicTreesBiomeModifier.java b/neoforge/src/main/java/com/dtteam/dynamictrees/worldgen/biomemodifier/AddDynamicTreesBiomeModifier.java index 64dc74014..1b5d4bfcc 100644 --- a/neoforge/src/main/java/com/dtteam/dynamictrees/worldgen/biomemodifier/AddDynamicTreesBiomeModifier.java +++ b/neoforge/src/main/java/com/dtteam/dynamictrees/worldgen/biomemodifier/AddDynamicTreesBiomeModifier.java @@ -1,7 +1,6 @@ package com.dtteam.dynamictrees.worldgen.biomemodifier; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.registry.DTRegistries; import com.dtteam.dynamictrees.registry.NeoForgeRegistryLoader; import com.mojang.serialization.MapCodec; @@ -18,7 +17,7 @@ public class AddDynamicTreesBiomeModifier implements BiomeModifier { @Override public void modify(Holder biome, Phase phase, ModifiableBiomeInfo.BiomeInfo.Builder builder) { - if (phase == Phase.ADD && Services.CONFIG.getBoolConfig(IConfigHelper.WORLD_GEN)) { + if (phase == Phase.ADD && DTConfigs.SERVER.worldGen.get()) { BiomeGenerationSettingsBuilder generationSettings = builder.getGenerationSettings(); var placedFeatures = ServerLifecycleHooks.getCurrentServer().registryAccess().registryOrThrow(Registries.PLACED_FEATURE); generationSettings.addFeature(GenerationStep.Decoration.VEGETAL_DECORATION, placedFeatures.getHolderOrThrow(DTRegistries.CAVE_ROOTED_TREE_PLACED_FEATURE)); diff --git a/neoforge/src/main/java/com/dtteam/dynamictrees/worldgen/biomemodifier/RunFeatureCancellersBiomeModifier.java b/neoforge/src/main/java/com/dtteam/dynamictrees/worldgen/biomemodifier/RunFeatureCancellersBiomeModifier.java index 8b1ddde10..bc9d62518 100644 --- a/neoforge/src/main/java/com/dtteam/dynamictrees/worldgen/biomemodifier/RunFeatureCancellersBiomeModifier.java +++ b/neoforge/src/main/java/com/dtteam/dynamictrees/worldgen/biomemodifier/RunFeatureCancellersBiomeModifier.java @@ -3,8 +3,7 @@ import com.dtteam.dynamictrees.DynamicTrees; import com.dtteam.dynamictrees.api.worldgen.BiomePropertySelectors; import com.dtteam.dynamictrees.api.worldgen.FeatureCanceller; -import com.dtteam.dynamictrees.platform.Services; -import com.dtteam.dynamictrees.platform.services.IConfigHelper; +import com.dtteam.dynamictrees.config.DTConfigs; import com.dtteam.dynamictrees.registry.NeoForgeRegistryLoader; import com.dtteam.dynamictrees.worldgen.BiomeDatabase; import com.dtteam.dynamictrees.worldgen.featurecancellation.FeatureCancellationRegistry; @@ -25,7 +24,7 @@ public class RunFeatureCancellersBiomeModifier implements BiomeModifier { @Override public void modify(Holder biome, Phase phase, ModifiableBiomeInfo.BiomeInfo.Builder builder) { - if (phase == Phase.REMOVE && Services.CONFIG.getBoolConfig(IConfigHelper.WORLD_GEN)) { + if (phase == Phase.REMOVE && DTConfigs.SERVER.worldGen.get()) { ResourceKey biomeKey = biome.unwrapKey().orElseThrow(); BiomeGenerationSettingsBuilder generationSettings = builder.getGenerationSettings(); diff --git a/settings.gradle b/settings.gradle index b390d1301..57a2a6d4d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -11,6 +11,7 @@ pluginManagement { } filter { includeGroup('net.fabricmc') + includeGroup('net.fabricmc.unpick') includeGroup('fabric-loom') } }