From d1fef12c2f90e21f329b48e0b075cf7325490e33 Mon Sep 17 00:00:00 2001 From: Aram Date: Thu, 24 Oct 2024 18:03:03 +0200 Subject: [PATCH] Conversion to Multi Loader for Fabric Support (#55) ## Description Refactored the entire code to the Multi-Loader Template for Fabric Support. I have also Optimized the Variant Loader by adding Folder Looping and Added a custom Logging + a bunch of Util Classes. ## Changes - **Util Classes**: Added various Utils Classes - **Multi-Loader Template**: Refactored the entire code to the Multi-Loader Template for Fabric Support. - **Folder Loop**: In stead of Specifying each JSON File, you can now Loop thru a folder to obtain all JSONs for the Variant Loader - **Custom Logging**: Added a Custom Logging System to improve the way of Debugging. - **Mixin**: Created a Mixin which minimizes the amount of methods a Developer needs to add from 6 to 3. - **Comments**: Went through all the comments and improved the Comments to ensure understandability and readability. - **Workflow**: Improved the Workflow(CI.yml) to support Fabric too. ## Type of Change - [ ] Bug fix - [x] New feature - [x] Documentation update ## Checklist - [x] My code follows the style guidelines of this project. [Contributing](https://github.com/MeAlam1/BlueLib/blob/1.20/CONTRIBUTING.md) - [x] I have performed a self-review of my own code. - [x] I have commented my code following the guidelines. [Contributing](https://github.com/MeAlam1/BlueLib/blob/1.20/CONTRIBUTING.md) - [x] My changes generate no new warnings. ## Related Issues * #48 - Variant Loader * #49 - Utility Classes * #50 - Multi Loader Template Support * #51 - Fabric Support * #52 - Forge Support * #53 - NeoForge Support * #54 - Custom Logging --------- Co-Authored-By: Bluedude <68448043+Dan6335@users.noreply.github.com> --- .../ISSUE_TEMPLATE/EnhancementTemplate.yml | 33 ++ .github/PULL_REQUEST_TEMPLATE.md | 2 +- .github/workflows/ci.yml | 26 +- .gitignore | 45 +- CONTRIBUTING.md | 190 +++++---- Forge/.gitattributes | 5 - Forge/.gitignore | 25 -- Forge/build.gradle | 133 ------ Forge/changelog.txt | 0 Forge/gradle.properties | 25 -- Forge/gradle/wrapper/gradle-wrapper.jar | Bin 62076 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 - Forge/gradlew | 245 ----------- Forge/gradlew.bat | 92 ----- Forge/settings.gradle | 19 - .../main/java/software/bluelib/BlueLib.java | 141 ------- .../bluelib/entity/variant/VariantLoader.java | 169 -------- .../entity/variant/VariantParameter.java | 131 ------ .../entity/variant/base/ParameterBase.java | 179 -------- .../bluelib/event/ReloadEventHandler.java | 78 ---- .../example/entity/dragon/DragonModel.java | 29 -- .../bluelib/example/entity/rex/RexModel.java | 29 -- .../bluelib/example/event/ClientEvents.java | 21 - .../bluelib/example/event/CommonModEvent.java | 18 - .../bluelib/example/proxy/ClientProxy.java | 19 - .../bluelib/example/proxy/CommonProxy.java | 13 - .../bluelib/exception/CouldNotLoadJSON.java | 64 --- .../interfaces/variant/IVariantEntity.java | 56 --- .../bluelib/utils/ParameterUtils.java | 181 -------- Forge/src/main/resources/pack.mcmeta | 6 - NeoForge/.gitignore | 26 -- NeoForge/build.gradle | 114 ----- NeoForge/changelog.txt | 0 NeoForge/gradle.properties | 25 -- NeoForge/settings.gradle | 11 - .../main/java/software/bluelib/BlueLib.java | 121 ------ .../bluelib/entity/variant/VariantLoader.java | 169 -------- .../entity/variant/VariantParameter.java | 131 ------ .../entity/variant/base/ParameterBase.java | 179 -------- .../bluelib/event/ReloadEventHandler.java | 78 ---- .../example/entity/dragon/DragonModel.java | 29 -- .../bluelib/example/entity/rex/RexModel.java | 29 -- .../bluelib/example/event/ClientEvents.java | 28 -- .../bluelib/exception/CouldNotLoadJSON.java | 64 --- .../bluelib/utils/ParameterUtils.java | 181 -------- .../src/main/resources/META-INF/mods.toml | 31 -- NeoForge/src/main/resources/pack.mcmeta | 6 - README.md | 34 ++ build.gradle | 94 +++++ common/build.gradle | 39 ++ .../java/software/bluelib/BlueLibCommon.java | 108 +++++ .../software/bluelib/BlueLibConstants.java | 93 +++++ .../interfaces/logging/ILogColorProvider.java | 36 ++ .../interfaces/platform/IPlatformHelper.java | 65 +++ .../interfaces/variant/IVariantAccessor.java | 37 ++ .../utils/conversion/CaseConverterUtils.java | 268 ++++++++++++ .../utils/conversion/MathConverterUtils.java | 151 +++++++ .../bluelib/utils/logging/BaseLogLevel.java | 74 ++++ .../bluelib/utils/logging/BaseLogger.java | 167 ++++++++ .../logging/DefaultLogColorProvider.java | 52 +++ .../bluelib/utils/logging/LoggerConfig.java | 106 +++++ .../bluelib/utils/math/AlgebraicUtils.java | 137 ++++++ .../bluelib/utils/math/GeometricUtils.java | 255 ++++++++++++ .../bluelib/utils/math/MatrixUtils.java | 132 ++++++ .../bluelib/utils/math/MiscUtils.java | 129 ++++++ .../bluelib/utils/math/RandomGenUtils.java | 135 ++++++ .../bluelib/utils/math/StatisticalUtils.java | 228 ++++++++++ common/src/main/resources/bluelib.png | Bin 0 -> 145980 bytes common/src/main/resources/pack.mcmeta | 6 + fabric/build.gradle | 85 ++++ .../main/java/software/bluelib/BlueLib.java | 69 ++++ .../bluelib/entity/variant/VariantLoader.java | 189 +++++++++ .../entity/variant/VariantParameter.java | 174 ++++++++ .../entity/variant/base/ParameterBase.java | 211 ++++++++++ .../bluelib/event/ReloadEventHandler.java | 79 ++++ .../example/entity/dragon/DragonEntity.java | 105 +++-- .../example/entity/dragon/DragonModel.java | 59 +++ .../example/entity/dragon/DragonRender.java | 14 +- .../bluelib/example/entity/rex/RexEntity.java | 107 +++-- .../bluelib/example/entity/rex/RexModel.java | 59 +++ .../bluelib/example/entity/rex/RexRender.java | 14 +- .../bluelib/example/event/ReloadHandler.java | 126 ++++++ .../bluelib/example/init/ClientInit.java | 38 ++ .../bluelib/example/init/ModEntities.java | 59 +++ .../interfaces/variant/IVariantEntity.java | 25 +- .../variant/base/IVariantEntityBase.java | 21 +- .../software/bluelib/json/JSONLoader.java | 36 +- .../software/bluelib/json/JSONMerger.java | 29 +- .../platform/FabricPlatformHelper.java | 65 +++ .../bluelib/utils/minecraft/ChunkUtils.java | 233 +++++++++++ .../bluelib/utils/variant/ParameterUtils.java | 184 +++++++++ ...luelib.interfaces.platform.IPlatformHelper | 1 + .../bluelib/animations/dragon.animation.json | 0 .../bluelib/animations/rex.animation.json | 0 .../assets/bluelib/geo/dragon.geo.json | 0 .../resources/assets/bluelib/geo/rex.geo.json | 0 .../resources/assets/bluelib/lang/en_us.json | 0 .../bluelib/textures/entity/dragon/blue.png | Bin 0 -> 676 bytes .../bluelib/textures/entity/dragon/bright.png | Bin .../bluelib/textures/entity/dragon/dark.png | Bin .../bluelib/textures/entity/dragon/pink.png | Bin 0 -> 676 bytes .../bluelib/textures/entity/rex/brown.png | Bin .../bluelib/textures/entity/rex/green.png | Bin .../bluelib/variant/entity/dragon/blue.json | 14 + .../variant/entity/dragon}/dragon.json | 0 .../bluelib/variant/entity/dragon/pink.json | 14 + .../data/bluelib/variant/entity/rex}/rex.json | 0 fabric/src/main/resources/fabric.mod.json | 36 ++ forge/build.gradle | 123 ++++++ .../main/java/software/bluelib/BlueLib.java | 99 +++++ .../bluelib/entity/variant/VariantLoader.java | 189 +++++++++ .../entity/variant/VariantParameter.java | 174 ++++++++ .../entity/variant/base/ParameterBase.java | 211 ++++++++++ .../bluelib/event/ReloadEventHandler.java | 79 ++++ .../example/entity/dragon/DragonEntity.java | 105 +++-- .../example/entity/dragon/DragonModel.java | 59 +++ .../example/entity/dragon/DragonRender.java | 14 +- .../bluelib/example/entity/rex/RexEntity.java | 107 +++-- .../bluelib/example/entity/rex/RexModel.java | 59 +++ .../bluelib/example/entity/rex/RexRender.java | 14 +- .../bluelib/example/event/ClientEvents.java | 40 ++ .../bluelib/example/event/CommonModEvent.java | 42 ++ .../bluelib/example/event/ReloadHandler.java | 46 +-- .../bluelib/example/init/ModEntities.java | 44 +- .../bluelib/example/proxy/ClientProxy.java | 37 ++ .../bluelib/example/proxy/CommonProxy.java | 40 ++ .../interfaces/variant/IVariantEntity.java | 59 +++ .../variant/base/IVariantEntityBase.java | 21 +- .../software/bluelib/json/JSONLoader.java | 36 +- .../software/bluelib/json/JSONMerger.java | 29 +- .../bluelib/platform/ForgePlatformHelper.java | 66 +++ .../bluelib/utils/minecraft/ChunkUtils.java | 233 +++++++++++ .../bluelib/utils/variant/ParameterUtils.java | 184 +++++++++ .../src/main/resources/META-INF/mods.toml | 18 +- ...luelib.interfaces.platform.IPlatformHelper | 1 + .../bluelib/animations/dragon.animation.json | 0 .../bluelib/animations/rex.animation.json | 0 .../assets/bluelib/geo/dragon.geo.json | 0 .../resources/assets/bluelib/geo/rex.geo.json | 0 .../resources/assets/bluelib/lang/en_us.json | 0 .../bluelib/textures/entity/dragon/blue.png | Bin 0 -> 676 bytes .../bluelib/textures/entity/dragon/bright.png | Bin .../bluelib/textures/entity/dragon/dark.png | Bin .../bluelib/textures/entity/dragon/pink.png | Bin 0 -> 676 bytes .../bluelib/textures/entity/rex/brown.png | Bin .../bluelib/textures/entity/rex/green.png | Bin .../bluelib.variant/entity/dragon/blue.json | 14 + .../entity/dragon}/dragon.json | 0 .../bluelib.variant/entity/dragon/pink.json | 14 + .../data/bluelib.variant/entity/rex}/rex.json | 0 gradle.properties | 32 ++ .../wrapper/gradle-wrapper.jar | Bin 43504 -> 43583 bytes .../wrapper/gradle-wrapper.properties | 2 +- NeoForge/gradlew => gradlew | 0 NeoForge/gradlew.bat => gradlew.bat | 0 neoforge/build.gradle | 91 ++++ .../main/java/software/bluelib/BlueLib.java | 70 ++++ .../bluelib/entity/variant/VariantLoader.java | 189 +++++++++ .../entity/variant/VariantParameter.java | 174 ++++++++ .../entity/variant/base/ParameterBase.java | 211 ++++++++++ .../bluelib/event/ReloadEventHandler.java | 79 ++++ .../example/entity/dragon/DragonEntity.java | 251 +++++++++++ .../example/entity/dragon/DragonModel.java | 59 +++ .../example/entity/dragon/DragonRender.java | 26 ++ .../bluelib/example/entity/rex/RexEntity.java | 251 +++++++++++ .../bluelib/example/entity/rex/RexModel.java | 59 +++ .../bluelib/example/entity/rex/RexRender.java | 26 ++ .../bluelib/example/event/ClientEvents.java | 57 +++ .../bluelib/example/event/ReloadHandler.java | 46 +-- .../bluelib/example/init/ModEntities.java | 39 +- .../interfaces/variant/IVariantEntity.java | 59 +++ .../variant/base/IVariantEntityBase.java | 66 +++ .../software/bluelib/json/JSONLoader.java | 73 ++++ .../software/bluelib/json/JSONMerger.java | 69 ++++ .../platform/NeoForgePlatformHelper.java | 66 +++ .../bluelib/utils/minecraft/ChunkUtils.java | 233 +++++++++++ .../bluelib/utils/variant/ParameterUtils.java | 184 +++++++++ .../src/main/resources/META-INF/mods.toml | 29 ++ ...luelib.interfaces.platform.IPlatformHelper | 1 + .../bluelib/animations/dragon.animation.json | 63 +++ .../bluelib/animations/rex.animation.json | 51 +++ .../assets/bluelib/geo/dragon.geo.json | 93 +++++ .../resources/assets/bluelib/geo/rex.geo.json | 390 ++++++++++++++++++ .../resources/assets/bluelib/lang/en_us.json | 4 + .../bluelib/textures/entity/dragon/blue.png | Bin 0 -> 676 bytes .../bluelib/textures/entity/dragon/bright.png | Bin 0 -> 676 bytes .../bluelib/textures/entity/dragon/dark.png | Bin 0 -> 676 bytes .../bluelib/textures/entity/dragon/pink.png | Bin 0 -> 676 bytes .../bluelib/textures/entity/rex/brown.png | Bin 0 -> 3454 bytes .../bluelib/textures/entity/rex/green.png | Bin 0 -> 1531 bytes .../bluelib/variant/entity/dragon/blue.json | 14 + .../bluelib/variant/entity/dragon/dragon.json | 24 ++ .../bluelib/variant/entity/dragon/pink.json | 14 + .../data/bluelib/variant/entity/rex/rex.json | 24 ++ settings.gradle | 33 ++ 195 files changed, 9558 insertions(+), 3345 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/EnhancementTemplate.yml delete mode 100644 Forge/.gitattributes delete mode 100644 Forge/.gitignore delete mode 100644 Forge/build.gradle delete mode 100644 Forge/changelog.txt delete mode 100644 Forge/gradle.properties delete mode 100644 Forge/gradle/wrapper/gradle-wrapper.jar delete mode 100644 Forge/gradle/wrapper/gradle-wrapper.properties delete mode 100644 Forge/gradlew delete mode 100644 Forge/gradlew.bat delete mode 100644 Forge/settings.gradle delete mode 100644 Forge/src/main/java/software/bluelib/BlueLib.java delete mode 100644 Forge/src/main/java/software/bluelib/entity/variant/VariantLoader.java delete mode 100644 Forge/src/main/java/software/bluelib/entity/variant/VariantParameter.java delete mode 100644 Forge/src/main/java/software/bluelib/entity/variant/base/ParameterBase.java delete mode 100644 Forge/src/main/java/software/bluelib/event/ReloadEventHandler.java delete mode 100644 Forge/src/main/java/software/bluelib/example/entity/dragon/DragonModel.java delete mode 100644 Forge/src/main/java/software/bluelib/example/entity/rex/RexModel.java delete mode 100644 Forge/src/main/java/software/bluelib/example/event/ClientEvents.java delete mode 100644 Forge/src/main/java/software/bluelib/example/event/CommonModEvent.java delete mode 100644 Forge/src/main/java/software/bluelib/example/proxy/ClientProxy.java delete mode 100644 Forge/src/main/java/software/bluelib/example/proxy/CommonProxy.java delete mode 100644 Forge/src/main/java/software/bluelib/exception/CouldNotLoadJSON.java delete mode 100644 Forge/src/main/java/software/bluelib/interfaces/variant/IVariantEntity.java delete mode 100644 Forge/src/main/java/software/bluelib/utils/ParameterUtils.java delete mode 100644 Forge/src/main/resources/pack.mcmeta delete mode 100644 NeoForge/.gitignore delete mode 100644 NeoForge/build.gradle delete mode 100644 NeoForge/changelog.txt delete mode 100644 NeoForge/gradle.properties delete mode 100644 NeoForge/settings.gradle delete mode 100644 NeoForge/src/main/java/software/bluelib/BlueLib.java delete mode 100644 NeoForge/src/main/java/software/bluelib/entity/variant/VariantLoader.java delete mode 100644 NeoForge/src/main/java/software/bluelib/entity/variant/VariantParameter.java delete mode 100644 NeoForge/src/main/java/software/bluelib/entity/variant/base/ParameterBase.java delete mode 100644 NeoForge/src/main/java/software/bluelib/event/ReloadEventHandler.java delete mode 100644 NeoForge/src/main/java/software/bluelib/example/entity/dragon/DragonModel.java delete mode 100644 NeoForge/src/main/java/software/bluelib/example/entity/rex/RexModel.java delete mode 100644 NeoForge/src/main/java/software/bluelib/example/event/ClientEvents.java delete mode 100644 NeoForge/src/main/java/software/bluelib/exception/CouldNotLoadJSON.java delete mode 100644 NeoForge/src/main/java/software/bluelib/utils/ParameterUtils.java delete mode 100644 NeoForge/src/main/resources/META-INF/mods.toml delete mode 100644 NeoForge/src/main/resources/pack.mcmeta create mode 100644 build.gradle create mode 100644 common/build.gradle create mode 100644 common/src/main/java/software/bluelib/BlueLibCommon.java create mode 100644 common/src/main/java/software/bluelib/BlueLibConstants.java create mode 100644 common/src/main/java/software/bluelib/interfaces/logging/ILogColorProvider.java create mode 100644 common/src/main/java/software/bluelib/interfaces/platform/IPlatformHelper.java create mode 100644 common/src/main/java/software/bluelib/interfaces/variant/IVariantAccessor.java create mode 100644 common/src/main/java/software/bluelib/utils/conversion/CaseConverterUtils.java create mode 100644 common/src/main/java/software/bluelib/utils/conversion/MathConverterUtils.java create mode 100644 common/src/main/java/software/bluelib/utils/logging/BaseLogLevel.java create mode 100644 common/src/main/java/software/bluelib/utils/logging/BaseLogger.java create mode 100644 common/src/main/java/software/bluelib/utils/logging/DefaultLogColorProvider.java create mode 100644 common/src/main/java/software/bluelib/utils/logging/LoggerConfig.java create mode 100644 common/src/main/java/software/bluelib/utils/math/AlgebraicUtils.java create mode 100644 common/src/main/java/software/bluelib/utils/math/GeometricUtils.java create mode 100644 common/src/main/java/software/bluelib/utils/math/MatrixUtils.java create mode 100644 common/src/main/java/software/bluelib/utils/math/MiscUtils.java create mode 100644 common/src/main/java/software/bluelib/utils/math/RandomGenUtils.java create mode 100644 common/src/main/java/software/bluelib/utils/math/StatisticalUtils.java create mode 100644 common/src/main/resources/bluelib.png create mode 100644 common/src/main/resources/pack.mcmeta create mode 100644 fabric/build.gradle create mode 100644 fabric/src/main/java/software/bluelib/BlueLib.java create mode 100644 fabric/src/main/java/software/bluelib/entity/variant/VariantLoader.java create mode 100644 fabric/src/main/java/software/bluelib/entity/variant/VariantParameter.java create mode 100644 fabric/src/main/java/software/bluelib/entity/variant/base/ParameterBase.java create mode 100644 fabric/src/main/java/software/bluelib/event/ReloadEventHandler.java rename {NeoForge => fabric}/src/main/java/software/bluelib/example/entity/dragon/DragonEntity.java (77%) create mode 100644 fabric/src/main/java/software/bluelib/example/entity/dragon/DragonModel.java rename {NeoForge => fabric}/src/main/java/software/bluelib/example/entity/dragon/DragonRender.java (57%) rename {NeoForge => fabric}/src/main/java/software/bluelib/example/entity/rex/RexEntity.java (76%) create mode 100644 fabric/src/main/java/software/bluelib/example/entity/rex/RexModel.java rename {NeoForge => fabric}/src/main/java/software/bluelib/example/entity/rex/RexRender.java (57%) create mode 100644 fabric/src/main/java/software/bluelib/example/event/ReloadHandler.java create mode 100644 fabric/src/main/java/software/bluelib/example/init/ClientInit.java create mode 100644 fabric/src/main/java/software/bluelib/example/init/ModEntities.java rename {NeoForge => fabric}/src/main/java/software/bluelib/interfaces/variant/IVariantEntity.java (59%) rename {NeoForge => fabric}/src/main/java/software/bluelib/interfaces/variant/base/IVariantEntityBase.java (72%) rename {NeoForge => fabric}/src/main/java/software/bluelib/json/JSONLoader.java (55%) rename {NeoForge => fabric}/src/main/java/software/bluelib/json/JSONMerger.java (68%) create mode 100644 fabric/src/main/java/software/bluelib/platform/FabricPlatformHelper.java create mode 100644 fabric/src/main/java/software/bluelib/utils/minecraft/ChunkUtils.java create mode 100644 fabric/src/main/java/software/bluelib/utils/variant/ParameterUtils.java create mode 100644 fabric/src/main/resources/META-INF/services/software.bluelib.interfaces.platform.IPlatformHelper rename {Forge => fabric}/src/main/resources/assets/bluelib/animations/dragon.animation.json (100%) rename {Forge => fabric}/src/main/resources/assets/bluelib/animations/rex.animation.json (100%) rename {Forge => fabric}/src/main/resources/assets/bluelib/geo/dragon.geo.json (100%) rename {Forge => fabric}/src/main/resources/assets/bluelib/geo/rex.geo.json (100%) rename {Forge => fabric}/src/main/resources/assets/bluelib/lang/en_us.json (100%) create mode 100644 fabric/src/main/resources/assets/bluelib/textures/entity/dragon/blue.png rename {Forge => fabric}/src/main/resources/assets/bluelib/textures/entity/dragon/bright.png (100%) rename {Forge => fabric}/src/main/resources/assets/bluelib/textures/entity/dragon/dark.png (100%) create mode 100644 fabric/src/main/resources/assets/bluelib/textures/entity/dragon/pink.png rename {Forge => fabric}/src/main/resources/assets/bluelib/textures/entity/rex/brown.png (100%) rename {Forge => fabric}/src/main/resources/assets/bluelib/textures/entity/rex/green.png (100%) create mode 100644 fabric/src/main/resources/data/bluelib/variant/entity/dragon/blue.json rename {Forge/src/main/resources/data/bluelib/variant/entity => fabric/src/main/resources/data/bluelib/variant/entity/dragon}/dragon.json (100%) create mode 100644 fabric/src/main/resources/data/bluelib/variant/entity/dragon/pink.json rename {Forge/src/main/resources/data/bluelib/variant/entity => fabric/src/main/resources/data/bluelib/variant/entity/rex}/rex.json (100%) create mode 100644 fabric/src/main/resources/fabric.mod.json create mode 100644 forge/build.gradle create mode 100644 forge/src/main/java/software/bluelib/BlueLib.java create mode 100644 forge/src/main/java/software/bluelib/entity/variant/VariantLoader.java create mode 100644 forge/src/main/java/software/bluelib/entity/variant/VariantParameter.java create mode 100644 forge/src/main/java/software/bluelib/entity/variant/base/ParameterBase.java create mode 100644 forge/src/main/java/software/bluelib/event/ReloadEventHandler.java rename {Forge => forge}/src/main/java/software/bluelib/example/entity/dragon/DragonEntity.java (77%) create mode 100644 forge/src/main/java/software/bluelib/example/entity/dragon/DragonModel.java rename {Forge => forge}/src/main/java/software/bluelib/example/entity/dragon/DragonRender.java (57%) rename {Forge => forge}/src/main/java/software/bluelib/example/entity/rex/RexEntity.java (76%) create mode 100644 forge/src/main/java/software/bluelib/example/entity/rex/RexModel.java rename {Forge => forge}/src/main/java/software/bluelib/example/entity/rex/RexRender.java (57%) create mode 100644 forge/src/main/java/software/bluelib/example/event/ClientEvents.java create mode 100644 forge/src/main/java/software/bluelib/example/event/CommonModEvent.java rename {Forge => forge}/src/main/java/software/bluelib/example/event/ReloadHandler.java (80%) rename {Forge => forge}/src/main/java/software/bluelib/example/init/ModEntities.java (54%) create mode 100644 forge/src/main/java/software/bluelib/example/proxy/ClientProxy.java create mode 100644 forge/src/main/java/software/bluelib/example/proxy/CommonProxy.java create mode 100644 forge/src/main/java/software/bluelib/interfaces/variant/IVariantEntity.java rename {Forge => forge}/src/main/java/software/bluelib/interfaces/variant/base/IVariantEntityBase.java (72%) rename {Forge => forge}/src/main/java/software/bluelib/json/JSONLoader.java (55%) rename {Forge => forge}/src/main/java/software/bluelib/json/JSONMerger.java (68%) create mode 100644 forge/src/main/java/software/bluelib/platform/ForgePlatformHelper.java create mode 100644 forge/src/main/java/software/bluelib/utils/minecraft/ChunkUtils.java create mode 100644 forge/src/main/java/software/bluelib/utils/variant/ParameterUtils.java rename {Forge => forge}/src/main/resources/META-INF/mods.toml (54%) create mode 100644 forge/src/main/resources/META-INF/services/software.bluelib.interfaces.platform.IPlatformHelper rename {NeoForge => forge}/src/main/resources/assets/bluelib/animations/dragon.animation.json (100%) rename {NeoForge => forge}/src/main/resources/assets/bluelib/animations/rex.animation.json (100%) rename {NeoForge => forge}/src/main/resources/assets/bluelib/geo/dragon.geo.json (100%) rename {NeoForge => forge}/src/main/resources/assets/bluelib/geo/rex.geo.json (100%) rename {NeoForge => forge}/src/main/resources/assets/bluelib/lang/en_us.json (100%) create mode 100644 forge/src/main/resources/assets/bluelib/textures/entity/dragon/blue.png rename {NeoForge => forge}/src/main/resources/assets/bluelib/textures/entity/dragon/bright.png (100%) rename {NeoForge => forge}/src/main/resources/assets/bluelib/textures/entity/dragon/dark.png (100%) create mode 100644 forge/src/main/resources/assets/bluelib/textures/entity/dragon/pink.png rename {NeoForge => forge}/src/main/resources/assets/bluelib/textures/entity/rex/brown.png (100%) rename {NeoForge => forge}/src/main/resources/assets/bluelib/textures/entity/rex/green.png (100%) create mode 100644 forge/src/main/resources/data/bluelib.variant/entity/dragon/blue.json rename {NeoForge/src/main/resources/data/bluelib/variant/entity => forge/src/main/resources/data/bluelib.variant/entity/dragon}/dragon.json (100%) create mode 100644 forge/src/main/resources/data/bluelib.variant/entity/dragon/pink.json rename {NeoForge/src/main/resources/data/bluelib/variant/entity => forge/src/main/resources/data/bluelib.variant/entity/rex}/rex.json (100%) create mode 100644 gradle.properties rename {NeoForge/gradle => gradle}/wrapper/gradle-wrapper.jar (89%) rename {NeoForge/gradle => gradle}/wrapper/gradle-wrapper.properties (93%) rename NeoForge/gradlew => gradlew (100%) rename NeoForge/gradlew.bat => gradlew.bat (100%) create mode 100644 neoforge/build.gradle create mode 100644 neoforge/src/main/java/software/bluelib/BlueLib.java create mode 100644 neoforge/src/main/java/software/bluelib/entity/variant/VariantLoader.java create mode 100644 neoforge/src/main/java/software/bluelib/entity/variant/VariantParameter.java create mode 100644 neoforge/src/main/java/software/bluelib/entity/variant/base/ParameterBase.java create mode 100644 neoforge/src/main/java/software/bluelib/event/ReloadEventHandler.java create mode 100644 neoforge/src/main/java/software/bluelib/example/entity/dragon/DragonEntity.java create mode 100644 neoforge/src/main/java/software/bluelib/example/entity/dragon/DragonModel.java create mode 100644 neoforge/src/main/java/software/bluelib/example/entity/dragon/DragonRender.java create mode 100644 neoforge/src/main/java/software/bluelib/example/entity/rex/RexEntity.java create mode 100644 neoforge/src/main/java/software/bluelib/example/entity/rex/RexModel.java create mode 100644 neoforge/src/main/java/software/bluelib/example/entity/rex/RexRender.java create mode 100644 neoforge/src/main/java/software/bluelib/example/event/ClientEvents.java rename {NeoForge => neoforge}/src/main/java/software/bluelib/example/event/ReloadHandler.java (80%) rename {NeoForge => neoforge}/src/main/java/software/bluelib/example/init/ModEntities.java (54%) create mode 100644 neoforge/src/main/java/software/bluelib/interfaces/variant/IVariantEntity.java create mode 100644 neoforge/src/main/java/software/bluelib/interfaces/variant/base/IVariantEntityBase.java create mode 100644 neoforge/src/main/java/software/bluelib/json/JSONLoader.java create mode 100644 neoforge/src/main/java/software/bluelib/json/JSONMerger.java create mode 100644 neoforge/src/main/java/software/bluelib/platform/NeoForgePlatformHelper.java create mode 100644 neoforge/src/main/java/software/bluelib/utils/minecraft/ChunkUtils.java create mode 100644 neoforge/src/main/java/software/bluelib/utils/variant/ParameterUtils.java create mode 100644 neoforge/src/main/resources/META-INF/mods.toml create mode 100644 neoforge/src/main/resources/META-INF/services/software.bluelib.interfaces.platform.IPlatformHelper create mode 100644 neoforge/src/main/resources/assets/bluelib/animations/dragon.animation.json create mode 100644 neoforge/src/main/resources/assets/bluelib/animations/rex.animation.json create mode 100644 neoforge/src/main/resources/assets/bluelib/geo/dragon.geo.json create mode 100644 neoforge/src/main/resources/assets/bluelib/geo/rex.geo.json create mode 100644 neoforge/src/main/resources/assets/bluelib/lang/en_us.json create mode 100644 neoforge/src/main/resources/assets/bluelib/textures/entity/dragon/blue.png create mode 100644 neoforge/src/main/resources/assets/bluelib/textures/entity/dragon/bright.png create mode 100644 neoforge/src/main/resources/assets/bluelib/textures/entity/dragon/dark.png create mode 100644 neoforge/src/main/resources/assets/bluelib/textures/entity/dragon/pink.png create mode 100644 neoforge/src/main/resources/assets/bluelib/textures/entity/rex/brown.png create mode 100644 neoforge/src/main/resources/assets/bluelib/textures/entity/rex/green.png create mode 100644 neoforge/src/main/resources/data/bluelib/variant/entity/dragon/blue.json create mode 100644 neoforge/src/main/resources/data/bluelib/variant/entity/dragon/dragon.json create mode 100644 neoforge/src/main/resources/data/bluelib/variant/entity/dragon/pink.json create mode 100644 neoforge/src/main/resources/data/bluelib/variant/entity/rex/rex.json create mode 100644 settings.gradle diff --git a/.github/ISSUE_TEMPLATE/EnhancementTemplate.yml b/.github/ISSUE_TEMPLATE/EnhancementTemplate.yml new file mode 100644 index 00000000..7675ff1a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/EnhancementTemplate.yml @@ -0,0 +1,33 @@ +name: Enhancement Template +description: Create a Enhancement! +title: "[Enhancement]: " +labels: ["Enhancement"] +body: + - type: markdown + attributes: + value: | + Thank you for making a Suggestion! + - type: textarea + id: what-should-we-add + attributes: + label: What do you want to have added? + description: | + Please tell us as detailed as possible what you want to have added to the library! + Feel free to also add Examples if they exist! + validations: + required: true + - type: dropdown + id: inspiration + attributes: + label: Inspiration + description: Is this idea used in other Mods/Games? + options: + - "Yes" + - "No" + validations: + required: true + - type: input + id: inspiration-where + attributes: + label: Where did you get the inspiration from? + description: If this idea is used in a different Mod/Game, please provide a list of the Games/Mods \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 2241979c..990c43ed 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -23,4 +23,4 @@ - [ ] My changes generate no new warnings. ## Related Issues - \ No newline at end of file + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f2692425..c90f2b3e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,6 +12,9 @@ jobs: build: runs-on: ubuntu-latest + env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE20: true + steps: - name: Checkout code uses: actions/checkout@v3 @@ -22,6 +25,11 @@ jobs: distribution: 'temurin' java-version: '17' + - name: Set up Node.js 20 + uses: actions/setup-node@v3 + with: + node-version: '20' + - name: Cache Gradle packages uses: actions/cache@v3 with: @@ -32,14 +40,20 @@ jobs: restore-keys: | ${{ runner.os }}-gradle- - - name: Grant execute permission for Gradle wrappers + - name: Generate Gradle wrapper if not present run: | - chmod +x NeoForge/gradlew - chmod +x Forge/gradlew + if [ ! -f "./gradlew" ]; then + gradle wrapper + fi + + - name: Grant execute permission for Gradle wrapper + run: chmod +x ./gradlew - name: Build for NeoForge - run: cd NeoForge && ./gradlew build + run: ./gradlew build -p neoforge - name: Build for Forge - run: cd Forge && ./gradlew build - \ No newline at end of file + run: ./gradlew build -p forge + + - name: Build for Fabric + run: ./gradlew build -p fabric diff --git a/.gitignore b/.gitignore index d2091a56..461017f6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,27 +1,24 @@ -# Compiled class file -*.class +# eclipse +bin +*.launch +.settings +.metadata +.classpath +.project -# Log file -*.log - -# BlueJ files -*.ctxt - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* -replay_pid* - -# Default ignored files +# idea +out +*.ipr +*.iws +*.iml .idea/* +!.idea/scopes + +# gradle +build +.gradle +# other +eclipse +run +runs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 053dcc87..ce3c0202 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,80 +15,115 @@ - All code should be commented using the `/** * * */` format. This ensures that comments are displayed when users hover over a method, class, or variable, even after the library is compiled. This helps maintain clarity and consistency in the documentation. - - **Structure**: - - **Class Descriptions**: - - **Key Methods:** List key methods provided by the class, using bullet points for easy readability. - - **Author:** If you have contributed to a Method/Class, put yourself as Co-author, if you have created an Method/Class, put yourself as Author. - - **Since Version:** Use the `@since` tag to indicate the version since which the class has been available. - **Example:** - ```java - /** - * A {@code JSONLoader} class responsible for loading and parsing JSON data from resources - * defined by {@link ResourceLocation} within a Minecraft mod environment. - *

- * Key Methods: - *

- * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - public class JSONLoader { - } - ``` - - **Method Descriptions**: Begin each comment with a brief description that typically starts with `A ...`, where the `...` represents a link to the relevant class or object using `{@link ClassName}`. If the method is `void`, use `{@code}` to refer to the method name instead. - - **Example for a Method**: - ```java - /** - * A {@link String} that retrieves the value of a custom parameter for a specific variant. - * - * @param pVariantName {@link String} - The variant name you want to see the custom parameter of. - * @param pParameterKey {@link String} - The parameter you want to see. - * @return The value of the custom parameter identified by {@code pParameterKey} - * for the variant specified by {@code pVariantName}. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - public String getCustomParameter(String pVariantName, String pParameterKey) { - // Method implementation - } - ``` - - - **Parameters**: Start each parameter description with `{@link TypeOfParameter} - [Comment]`. If the parameter is referenced within the comment, enclose it in a code block using `{@code}`. - - **Example for Parameters**: - ```java - /** - * A {@link String} that retrieves the value of a custom parameter for a specific variant. - * - * @param pVariantName {@link String} - The variant name you want to see the custom parameter of. - * @param pParameterKey {@link String} - The parameter you want to see. - * @return The value of the custom parameter identified by {@code pParameterKey} - * for the variant specified by {@code pVariantName}. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - public String getCustomParameter(String pVariantName, String pParameterKey) { - // Method implementation - } - ``` + - **Structure**: + - **Class Descriptions**: + - **Start with:** Start the Comment with A(n) {@code privacy/static/final} {@code/link Class/MethodType} [Description of the Class/Method]. + - **Key Methods:** List key methods provided by the class, using bullet points for easy readability. + - **Author:** If you have contributed to a Method/Class, feel free to add yourself to the author tag. + - **Since Version:** Use the `@since` tag to indicate the version since which the Method/Class has been available. + - **Version:** Use the `@version` tag to indicate the version since when the class has last been updated. + **Example:** + ```java + /** + * A {@code public abstract base class} for managing a collection of {@link #parameters}. + *

+ * This {@code class} provides methods to add, retrieve, remove, and manipulate {@link #parameters} stored as key-value pairs. + *

+ * Key Methods: + * + * + * @author MeAlam + * @since 1.0.0 + * @version 1.0.0 + */ + ``` + - **Start with:** Start the Comment with A(n) {@code privacy/static/final} {@code/link Class/MethodType} [Description of the Class/Method]. + - **Example for a Method**: + ```java + /** + * A {@code protected static void} that registers entity variants from specified locations. + *

+ * This method attempts to load variants from both mod and datapack locations. It logs status information and + * handles exceptions that occur during the loading process. + *

+ *

+ * Parameters: + *

+ * + * Exception Handling: + * + * + * @param pFolderPath {@link String} - The folder path location within the mod or datapack where variants are stored. + * @param pServer {@link MinecraftServer} - The server instance of the current world. + * @param pModID {@link String} - The mod ID used to locate the entity variant resources. (Use your Mod's ID) + * @param pEntityName {@link String} - The entity name to load. + * @throws JsonParseException if there is an error parsing the JSON files. + * @throws RuntimeException if an unexpected error occurs during the registration process. + * @author MeAlam + * @see MinecraftServer + * @see ResourceLocation + * @see VariantLoader + * @since 1.0.0 + */ + protected static void registerEntityVariants(String pFolderPath, MinecraftServer pServer, String pModID, String pEntityName) { + } + ``` + + - **Parameters**: Start each parameter description with `{@link TypeOfParameter} - [Comment]`. If the parameter is referenced within the comment, enclose it in a code block using `{@code}`. + - **Example for Parameters**: + ```java + /** + * A {@link String} that retrieves the value of a custom parameter for a specific variant. + * + * @param pVariantName {@link String} - The variant name you want to see the custom parameter of. + * @param pParameterKey {@link String} - The parameter you want to see. + * @return The value of the custom parameter identified by {@code pParameterKey} + * for the variant specified by {@code pVariantName}. + * @author MeAlam + * @since 1.0.0 + */ + public String getCustomParameter(String pVariantName, String pParameterKey) { + // Method implementation + } + ``` - **General Guidelines**: - Always ensure that comments are clear, concise, and provide sufficient information to understand the code. - When referencing variables or constants, use `{@code}` to wrap them within the comment. - Use `{@link}` to refer to classes, methods, or any other Java elements where appropriate. - - Key Methods: In class-level comments, list out key methods provided by the class, which can help users quickly understand the main functionalities. + - Key Methods: In class-level comments, list out key methods provided by the class, which can help users quickly understand the main functionalities. - Versioning: Include the `@since` tag in both class-level and method-level comments to indicate the version since which the class or method has been available. - - If you update a Class/Method, please add/update the `@version` to indicate it has been changed. + - If you update a Class, please add/update the `@version` to indicate it has been changed. - Copyright: Each file should start with `// Copyright (c) BlueLib. Licensed under the MIT License.` + - Tags: Use `@see` to link to the correct Wiki Documentation page if it exists. + - Logging: Log every step using `BaseLogger.log`, Always remove the `@EnableLogging` annotation/disable the logging before committing. + - Error Handling: Always ensure that errors and warnings are logged using appropriate logging levels. Critical steps must be logged at least with `BaseLogger.log(BaseLogLevel.Error)` to keep a trail of execution. ### Deprecation - If you optimize a method, variable, or class and determine that it is no longer necessary for the library, mark it as `@Deprecated` instead of removing it. This only applies to elements that have been included in previous released versions of the library. - - Include a **strong TODO comment** explaining why it is deprecated and any further action required, such as testing or eventual removal. +- Include an **`@see`** that links to the New Method - **Example**: ```java /** @@ -97,8 +132,8 @@ * TODO: Testing with Multiple Entities and Datapacks required before Deletion/Refactoring.
* @return A map containing the parameters added to this builder. * @author MeAlam - * @Co-author Dan * @since 1.0.0 + * @see #newMethod() */ @Deprecated public Map build() { @@ -134,40 +169,31 @@ - Visual Studio Code (VSC) - Eclipse (Recommended) -5. **Publish to Local Maven Repository** - - Run the following command from the library folder (e.g., `NeoForge`, `Forge`, `Fabric`) to publish the library to your local Maven repository. - - **Example**: - ```bash - ./gradlew publishToMavenLocal - ``` - - This allows you to test the library locally. - -6. **Modify the Library** +5. **Modify the Library** - Make the necessary changes to the respective library folder you are working on. Ensure you adhere to the coding conventions described above. -7. **Test Your Changes** - - Before committing, test your changes by running the game using the appropriate test mod loader folder: - - `TestMods/TestNeoForge` - - `TestMods/TestForge` - - `TestMods/TestFabric` +6. **Test Your Changes** + - Before committing, test your changes by running the game using the appropriate test mod loader folder. + - Use the `example` package to test your changes. + - If no code is available to test, create new test code in the `example` package. - Ensure that your changes do not introduce any issues or regressions. -8. **Commit Your Changes** - - Once you are satisfied with your changes, commit them from the root folder (`BlueLib`). +7. **Commit Your Changes** + - Once you are satisfied with your changes, commit them to your branch. - Write clear and concise commit messages explaining the changes made. - **Example**: ```bash git commit -am "Improved logging functionality and deprecated old log method" ``` -9. **Push to Your Fork** +8. **Push to Your Fork** - Push your branch to your fork on GitHub. - **Example**: ```bash git push origin feature/improve-logging ``` -10. **Create a Pull Request (PR)** +9. **Create a Pull Request (PR)** - Navigate to your fork on GitHub and create a Pull Request to the main repository. Provide a detailed description of the changes made and why they are necessary. ## Contributor License Agreement (CLA) @@ -194,4 +220,4 @@ If you have any questions or need further clarification, please reach out to the Thank you for contributing to BlueLib and helping to make it better! ---- +--- \ No newline at end of file diff --git a/Forge/.gitattributes b/Forge/.gitattributes deleted file mode 100644 index f811f6ae..00000000 --- a/Forge/.gitattributes +++ /dev/null @@ -1,5 +0,0 @@ -# Disable autocrlf on generated files, they always generate with LF -# Add any extra files or paths here to make git stop saying they -# are changed when only line endings change. -src/generated/**/.cache/cache text eol=lf -src/generated/**/*.json text eol=lf diff --git a/Forge/.gitignore b/Forge/.gitignore deleted file mode 100644 index 12f86447..00000000 --- a/Forge/.gitignore +++ /dev/null @@ -1,25 +0,0 @@ -# eclipse -bin -*.launch -.settings -.metadata -.classpath -.project - -# idea -out -*.ipr -*.iws -*.iml -.idea - -# gradle -build -.gradle - -# other -eclipse -run - -# Files from Forge MDK -forge*changelog.txt diff --git a/Forge/build.gradle b/Forge/build.gradle deleted file mode 100644 index 340258b7..00000000 --- a/Forge/build.gradle +++ /dev/null @@ -1,133 +0,0 @@ -plugins { - id 'eclipse' - id 'idea' - id 'maven-publish' - id 'net.minecraftforge.gradle' version '[6.0.16,6.2)' - id 'org.spongepowered.mixin' version '0.7.+' -} - -version = mod_version -group = mod_group_id - -repositories { - maven { - name = 'GeckoLib' - url 'https://dl.cloudsmith.io/public/geckolib3/geckolib/maven/' - content { - includeGroupByRegex("software\\.bernie.*") - includeGroup("com.eliotlash.mclib") - } - } - maven { - url "https://cursemaven.com" - } - mavenCentral() - mavenLocal() -} - -base { - archivesName = "${mod_id}-forge-${minecraft_version}" -} - -java.toolchain.languageVersion = JavaLanguageVersion.of(java_version) - -println "Java: ${System.getProperty 'java.version'}, JVM: ${System.getProperty 'java.vm.version'} (${System.getProperty 'java.vendor'}), Arch: ${System.getProperty 'os.arch'}" -minecraft { - mappings channel: mapping_channel, version: mapping_version - - copyIdeResources = true - - runs { - configureEach { - workingDirectory project.file('run') - - property 'forge.logging.markers', 'REGISTRIES' - - property 'forge.logging.console.level', 'debug' - } - - client { - property 'forge.enabledGameTestNamespaces', mod_id - mods { - geckolib { - source sourceSets.main - } - } - } - - server { - property 'forge.enabledGameTestNamespaces', mod_id - args '--nogui' - } - - gameTestServer { - property 'forge.enabledGameTestNamespaces', mod_id - } - - data { - workingDirectory project.file('run-data') - - args '--mod', mod_id, '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources/') - } - } -} - -sourceSets.main.resources { srcDir 'src/generated/resources' } - -dependencies { - minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}" - //compileOnly fg.deobf("software.bernie.geckolib:geckolib-forge-${minecraft_version}:${geckolib_version}") - //runtimeOnly fg.deobf("software.bernie.geckolib:geckolib-forge-${minecraft_version}:${geckolib_version}") - runtimeOnly "curse.maven:geckolib-388172:${geckolib_file}" - compileOnly "curse.maven:geckolib-388172:${geckolib_file}" -} - -tasks.named('processResources', ProcessResources).configure { - def replaceProperties = [ - minecraft_version: minecraft_version, - minecraft_version_range: minecraft_version_range, - forge_version: forge_version, - forge_version_range: forge_version_range, - loader_version_range: loader_version_range, - mod_id: mod_id, - mod_name: mod_name, - mod_license: mod_license, - mod_version: mod_version, - mod_authors: mod_authors, - mod_description: mod_description, - ] - inputs.properties replaceProperties - - filesMatching(['META-INF/mods.toml', 'pack.mcmeta']) { - expand replaceProperties + [project: project] - } -} - -tasks.named('jar', Jar).configure { - manifest { - attributes([ - 'Specification-Title' : mod_id, - 'Specification-Vendor' : mod_authors, - 'Specification-Version' : mod_version, - 'Implementation-Title' : project.name, - 'Implementation-Version' : project.jar.archiveVersion, - 'Implementation-Vendor' : mod_authors - ]) - } - - finalizedBy 'reobfJar' -} - -tasks.withType(JavaCompile).configureEach { - options.encoding = 'UTF-8' -} - -eclipse { - synchronizationTasks 'genEclipseRuns' -} - -sourceSets.each { - def dir = layout.buildDirectory.dir("sourcesSets/$it.name") - it.output.resourcesDir = dir - it.java.destinationDirectory = dir -} \ No newline at end of file diff --git a/Forge/changelog.txt b/Forge/changelog.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/Forge/gradle.properties b/Forge/gradle.properties deleted file mode 100644 index e4525856..00000000 --- a/Forge/gradle.properties +++ /dev/null @@ -1,25 +0,0 @@ -org.gradle.jvmargs=-Xmx3G -org.gradle.daemon=false - -## Environment Properties - -minecraft_version=1.20 -minecraft_version_range=[1.20,1.22) -forge_version=46.0.14 -forge_version_range=[46,) -loader_version_range=[46,) -mapping_channel=official -mapping_version=1.20 -geckolib_version=4.2 -geckolib_file=4573048 -java_version=17 - -## Mod Properties - -mod_id=bluelib -mod_name=BlueLib -mod_license=MIT License -mod_version=1.0.0 -mod_group_id=software.bluelib -mod_authors=Dan, Aram -mod_description=BlueLib is an All round Minecraft mod library that offers data-driven features, allowing users to implement and customize its features with full freedom. \nIt supports both Resource and Datapacks, ensuring seamless integration and flexibility. \ No newline at end of file diff --git a/Forge/gradle/wrapper/gradle-wrapper.jar b/Forge/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index c1962a79e29d3e0ab67b14947c167a862655af9b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62076 zcmb5VV{~QRw)Y#`wrv{~+qP{x72B%VwzFc}c2cp;N~)5ZbDrJayPv(!dGEd-##*zr z)#n-$y^sH|_dchh3@8{H5D*j;5D<{i*8l5IFJ|DjL!e)upfGNX(kojugZ3I`oH1PvW`wFW_ske0j@lB9bX zO;2)`y+|!@X(fZ1<2n!Qx*)_^Ai@Cv-dF&(vnudG?0CsddG_&Wtae(n|K59ew)6St z#dj7_(Cfwzh$H$5M!$UDd8=4>IQsD3xV=lXUq($;(h*$0^yd+b{qq63f0r_de#!o_ zXDngc>zy`uor)4A^2M#U*DC~i+dc<)Tb1Tv&~Ev@oM)5iJ4Sn#8iRw16XXuV50BS7 zdBL5Mefch(&^{luE{*5qtCZk$oFr3RH=H!c3wGR=HJ(yKc_re_X9pD` zJ;uxPzUfVpgU>DSq?J;I@a+10l0ONXPcDkiYcihREt5~T5Gb}sT0+6Q;AWHl`S5dV>lv%-p9l#xNNy7ZCr%cyqHY%TZ8Q4 zbp&#ov1*$#grNG#1vgfFOLJCaNG@K|2!W&HSh@3@Y%T?3YI75bJp!VP*$*!< z;(ffNS_;@RJ`=c7yX04!u3JP*<8jeqLHVJu#WV&v6wA!OYJS4h<_}^QI&97-;=ojW zQ-1t)7wnxG*5I%U4)9$wlv5Fr;cIizft@&N+32O%B{R1POm$oap@&f| zh+5J{>U6ftv|vAeKGc|zC=kO(+l7_cLpV}-D#oUltScw})N>~JOZLU_0{Ka2e1evz z{^a*ZrLr+JUj;)K&u2CoCAXLC2=fVScI(m_p~0FmF>>&3DHziouln?;sxW`NB}cSX z8?IsJB)Z=aYRz!X=yJn$kyOWK%rCYf-YarNqKzmWu$ZvkP12b4qH zhS9Q>j<}(*frr?z<%9hl*i^#@*O2q(Z^CN)c2c z>1B~D;@YpG?G!Yk+*yn4vM4sO-_!&m6+`k|3zd;8DJnxsBYtI;W3We+FN@|tQ5EW= z!VU>jtim0Mw#iaT8t_<+qKIEB-WwE04lBd%Letbml9N!?SLrEG$nmn7&W(W`VB@5S zaY=sEw2}i@F_1P4OtEw?xj4@D6>_e=m=797#hg}f*l^`AB|Y0# z9=)o|%TZFCY$SzgSjS|8AI-%J4x}J)!IMxY3_KYze`_I=c1nmrk@E8c9?MVRu)7+Ue79|)rBX7tVB7U|w4*h(;Gi3D9le49B38`wuv zp7{4X^p+K4*$@gU(Tq3K1a#3SmYhvI42)GzG4f|u zwQFT1n_=n|jpi=70-yE9LA+d*T8u z`=VmmXJ_f6WmZveZPct$Cgu^~gFiyL>Lnpj*6ee>*0pz=t$IJ}+rE zsf@>jlcG%Wx;Cp5x)YSVvB1$yyY1l&o zvwX=D7k)Dn;ciX?Z)Pn8$flC8#m`nB&(8?RSdBvr?>T9?E$U3uIX7T?$v4dWCa46 z+&`ot8ZTEgp7G+c52oHJ8nw5}a^dwb_l%MOh(ebVj9>_koQP^$2B~eUfSbw9RY$_< z&DDWf2LW;b0ZDOaZ&2^i^g+5uTd;GwO(-bbo|P^;CNL-%?9mRmxEw~5&z=X^Rvbo^WJW=n_%*7974RY}JhFv46> zd}`2|qkd;89l}R;i~9T)V-Q%K)O=yfVKNM4Gbacc7AOd>#^&W&)Xx!Uy5!BHnp9kh z`a(7MO6+Ren#>R^D0K)1sE{Bv>}s6Rb9MT14u!(NpZOe-?4V=>qZ>}uS)!y~;jEUK z&!U7Fj&{WdgU#L0%bM}SYXRtM5z!6M+kgaMKt%3FkjWYh=#QUpt$XX1!*XkpSq-pl zhMe{muh#knk{9_V3%qdDcWDv}v)m4t9 zQhv{;} zc{}#V^N3H>9mFM8`i`0p+fN@GqX+kl|M94$BK3J-X`Hyj8r!#x6Vt(PXjn?N)qedP z=o1T^#?1^a{;bZ&x`U{f?}TMo8ToN zkHj5v|}r}wDEi7I@)Gj+S1aE-GdnLN+$hw!=DzglMaj#{qjXi_dwpr|HL(gcCXwGLEmi|{4&4#OZ4ChceA zKVd4K!D>_N=_X;{poT~4Q+!Le+ZV>=H7v1*l%w`|`Dx8{)McN@NDlQyln&N3@bFpV z_1w~O4EH3fF@IzJ9kDk@7@QctFq8FbkbaH7K$iX=bV~o#gfh?2JD6lZf(XP>~DACF)fGFt)X%-h1yY~MJU{nA5 ze2zxWMs{YdX3q5XU*9hOH0!_S24DOBA5usB+Ws$6{|AMe*joJ?RxfV}*7AKN9V*~J zK+OMcE@bTD>TG1*yc?*qGqjBN8mgg@h1cJLDv)0!WRPIkC` zZrWXrceVw;fB%3`6kq=a!pq|hFIsQ%ZSlo~)D z|64!aCnw-?>}AG|*iOl44KVf8@|joXi&|)1rB;EQWgm+iHfVbgllP$f!$Wf42%NO5b(j9Bw6L z;0dpUUK$5GX4QbMlTmLM_jJt!ur`_0~$b#BB7FL*%XFf<b__1o)Ao3rlobbN8-(T!1d-bR8D3S0@d zLI!*GMb5s~Q<&sjd}lBb8Nr0>PqE6_!3!2d(KAWFxa{hm`@u|a(%#i(#f8{BP2wbs zt+N_slWF4IF_O|{w`c~)Xvh&R{Au~CFmW#0+}MBd2~X}t9lz6*E7uAD`@EBDe$>7W zzPUkJx<`f$0VA$=>R57^(K^h86>09?>_@M(R4q($!Ck6GG@pnu-x*exAx1jOv|>KH zjNfG5pwm`E-=ydcb+3BJwuU;V&OS=6yM^4Jq{%AVqnTTLwV`AorIDD}T&jWr8pB&j28fVtk_y*JRP^t@l*($UZ z6(B^-PBNZ+z!p?+e8@$&jCv^EWLb$WO=}Scr$6SM*&~B95El~;W_0(Bvoha|uQ1T< zO$%_oLAwf1bW*rKWmlD+@CP&$ObiDy=nh1b2ejz%LO9937N{LDe7gle4i!{}I$;&Y zkexJ9Ybr+lrCmKWg&}p=`2&Gf10orS?4$VrzWidT=*6{KzOGMo?KI0>GL0{iFWc;C z+LPq%VH5g}6V@-tg2m{C!-$fapJ9y}c$U}aUmS{9#0CM*8pC|sfer!)nG7Ji>mfRh z+~6CxNb>6eWKMHBz-w2{mLLwdA7dA-qfTu^A2yG1+9s5k zcF=le_UPYG&q!t5Zd_*E_P3Cf5T6821bO`daa`;DODm8Ih8k89=RN;-asHIigj`n=ux>*f!OC5#;X5i;Q z+V!GUy0|&Y_*8k_QRUA8$lHP;GJ3UUD08P|ALknng|YY13)}!!HW@0z$q+kCH%xet zlWf@BXQ=b=4}QO5eNnN~CzWBbHGUivG=`&eWK}beuV*;?zt=P#pM*eTuy3 zP}c#}AXJ0OIaqXji78l;YrP4sQe#^pOqwZUiiN6^0RCd#D271XCbEKpk`HI0IsN^s zES7YtU#7=8gTn#lkrc~6)R9u&SX6*Jk4GFX7){E)WE?pT8a-%6P+zS6o&A#ml{$WX zABFz#i7`DDlo{34)oo?bOa4Z_lNH>n;f0nbt$JfAl~;4QY@}NH!X|A$KgMmEsd^&Y zt;pi=>AID7ROQfr;MsMtClr5b0)xo|fwhc=qk33wQ|}$@?{}qXcmECh>#kUQ-If0$ zseb{Wf4VFGLNc*Rax#P8ko*=`MwaR-DQ8L8V8r=2N{Gaips2_^cS|oC$+yScRo*uF zUO|5=?Q?{p$inDpx*t#Xyo6=s?bbN}y>NNVxj9NZCdtwRI70jxvm3!5R7yiWjREEd zDUjrsZhS|P&|Ng5r+f^kA6BNN#|Se}_GF>P6sy^e8kBrgMv3#vk%m}9PCwUWJg-AD zFnZ=}lbi*mN-AOm zCs)r=*YQAA!`e#1N>aHF=bb*z*hXH#Wl$z^o}x##ZrUc=kh%OHWhp=7;?8%Xj||@V?1c ziWoaC$^&04;A|T)!Zd9sUzE&$ODyJaBpvqsw19Uiuq{i#VK1!htkdRWBnb z`{rat=nHArT%^R>u#CjjCkw-7%g53|&7z-;X+ewb?OLWiV|#nuc8mp*LuGSi3IP<<*Wyo9GKV7l0Noa4Jr0g3p_$ z*R9{qn=?IXC#WU>48-k5V2Oc_>P;4_)J@bo1|pf=%Rcbgk=5m)CJZ`caHBTm3%!Z9 z_?7LHr_BXbKKr=JD!%?KhwdYSdu8XxPoA{n8^%_lh5cjRHuCY9Zlpz8g+$f@bw@0V z+6DRMT9c|>1^3D|$Vzc(C?M~iZurGH2pXPT%F!JSaAMdO%!5o0uc&iqHx?ImcX6fI zCApkzc~OOnfzAd_+-DcMp&AOQxE_EsMqKM{%dRMI5`5CT&%mQO?-@F6tE*xL?aEGZ z8^wH@wRl`Izx4sDmU>}Ym{ybUm@F83qqZPD6nFm?t?(7>h*?`fw)L3t*l%*iw0Qu#?$5eq!Qc zpQvqgSxrd83NsdO@lL6#{%lsYXWen~d3p4fGBb7&5xqNYJ)yn84!e1PmPo7ChVd%4 zHUsV0Mh?VpzZD=A6%)Qrd~i7 z96*RPbid;BN{Wh?adeD_p8YU``kOrGkNox3D9~!K?w>#kFz!4lzOWR}puS(DmfjJD z`x0z|qB33*^0mZdM&6$|+T>fq>M%yoy(BEjuh9L0>{P&XJ3enGpoQRx`v6$txXt#c z0#N?b5%srj(4xmPvJxrlF3H%OMB!jvfy z;wx8RzU~lb?h_}@V=bh6p8PSb-dG|-T#A?`c&H2`_!u+uenIZe`6f~A7r)`9m8atC zt(b|6Eg#!Q*DfRU=Ix`#B_dK)nnJ_+>Q<1d7W)eynaVn`FNuN~%B;uO2}vXr5^zi2 z!ifIF5@Zlo0^h~8+ixFBGqtweFc`C~JkSq}&*a3C}L?b5Mh-bW=e)({F_g4O3 zb@SFTK3VD9QuFgFnK4Ve_pXc3{S$=+Z;;4+;*{H}Rc;845rP?DLK6G5Y-xdUKkA6E3Dz&5f{F^FjJQ(NSpZ8q-_!L3LL@H* zxbDF{gd^U3uD;)a)sJwAVi}7@%pRM&?5IaUH%+m{E)DlA_$IA1=&jr{KrhD5q&lTC zAa3c)A(K!{#nOvenH6XrR-y>*4M#DpTTOGQEO5Jr6kni9pDW`rvY*fs|ItV;CVITh z=`rxcH2nEJpkQ^(;1c^hfb8vGN;{{oR=qNyKtR1;J>CByul*+=`NydWnSWJR#I2lN zTvgnR|MBx*XFsfdA&;tr^dYaqRZp*2NwkAZE6kV@1f{76e56eUmGrZ>MDId)oqSWw z7d&r3qfazg+W2?bT}F)4jD6sWaw`_fXZGY&wnGm$FRPFL$HzVTH^MYBHWGCOk-89y zA+n+Q6EVSSCpgC~%uHfvyg@ufE^#u?JH?<73A}jj5iILz4Qqk5$+^U(SX(-qv5agK znUkfpke(KDn~dU0>gdKqjTkVk`0`9^0n_wzXO7R!0Thd@S;U`y)VVP&mOd-2 z(hT(|$=>4FY;CBY9#_lB$;|Wd$aOMT5O_3}DYXEHn&Jrc3`2JiB`b6X@EUOD zVl0S{ijm65@n^19T3l%>*;F(?3r3s?zY{thc4%AD30CeL_4{8x6&cN}zN3fE+x<9; zt2j1RRVy5j22-8U8a6$pyT+<`f+x2l$fd_{qEp_bfxfzu>ORJsXaJn4>U6oNJ#|~p z`*ZC&NPXl&=vq2{Ne79AkQncuxvbOG+28*2wU$R=GOmns3W@HE%^r)Fu%Utj=r9t` zd;SVOnA(=MXgnOzI2@3SGKHz8HN~Vpx&!Ea+Df~`*n@8O=0!b4m?7cE^K*~@fqv9q zF*uk#1@6Re_<^9eElgJD!nTA@K9C732tV~;B`hzZ321Ph=^BH?zXddiu{Du5*IPg} zqDM=QxjT!Rp|#Bkp$(mL)aar)f(dOAXUiw81pX0DC|Y4;>Vz>>DMshoips^8Frdv} zlTD=cKa48M>dR<>(YlLPOW%rokJZNF2gp8fwc8b2sN+i6&-pHr?$rj|uFgktK@jg~ zIFS(%=r|QJ=$kvm_~@n=ai1lA{7Z}i+zj&yzY+!t$iGUy|9jH#&oTNJ;JW-3n>DF+ z3aCOzqn|$X-Olu_p7brzn`uk1F*N4@=b=m;S_C?#hy{&NE#3HkATrg?enaVGT^$qIjvgc61y!T$9<1B@?_ibtDZ{G zeXInVr5?OD_nS_O|CK3|RzzMmu+8!#Zb8Ik;rkIAR%6?$pN@d<0dKD2c@k2quB%s( zQL^<_EM6ow8F6^wJN1QcPOm|ehA+dP(!>IX=Euz5qqIq}Y3;ibQtJnkDmZ8c8=Cf3 zu`mJ!Q6wI7EblC5RvP*@)j?}W=WxwCvF3*5Up_`3*a~z$`wHwCy)2risye=1mSp%p zu+tD6NAK3o@)4VBsM!@);qgsjgB$kkCZhaimHg&+k69~drbvRTacWKH;YCK(!rC?8 zP#cK5JPHSw;V;{Yji=55X~S+)%(8fuz}O>*F3)hR;STU`z6T1aM#Wd+FP(M5*@T1P z^06O;I20Sk!bxW<-O;E081KRdHZrtsGJflFRRFS zdi5w9OVDGSL3 zNrC7GVsGN=b;YH9jp8Z2$^!K@h=r-xV(aEH@#JicPy;A0k1>g1g^XeR`YV2HfmqXY zYbRwaxHvf}OlCAwHoVI&QBLr5R|THf?nAevV-=~V8;gCsX>jndvNOcFA+DI+zbh~# zZ7`qNk&w+_+Yp!}j;OYxIfx_{f0-ONc?mHCiCUak=>j>~>YR4#w# zuKz~UhT!L~GfW^CPqG8Lg)&Rc6y^{%3H7iLa%^l}cw_8UuG;8nn9)kbPGXS}p3!L_ zd#9~5CrH8xtUd?{d2y^PJg+z(xIfRU;`}^=OlehGN2=?}9yH$4Rag}*+AWotyxfCJ zHx=r7ZH>j2kV?%7WTtp+-HMa0)_*DBBmC{sd$)np&GEJ__kEd`xB5a2A z*J+yx>4o#ZxwA{;NjhU*1KT~=ZK~GAA;KZHDyBNTaWQ1+;tOFFthnD)DrCn`DjBZ% zk$N5B4^$`n^jNSOr=t(zi8TN4fpaccsb`zOPD~iY=UEK$0Y70bG{idLx@IL)7^(pL z{??Bnu=lDeguDrd%qW1)H)H`9otsOL-f4bSu};o9OXybo6J!Lek`a4ff>*O)BDT_g z<6@SrI|C9klY(>_PfA^qai7A_)VNE4c^ZjFcE$Isp>`e5fLc)rg@8Q_d^Uk24$2bn z9#}6kZ2ZxS9sI(RqT7?El2@B+($>eBQrNi_k#CDJ8D9}8$mmm z4oSKO^F$i+NG)-HE$O6s1--6EzJa?C{x=QgK&c=)b(Q9OVoAXYEEH20G|q$}Hue%~ zO3B^bF=t7t48sN zWh_zA`w~|){-!^g?6Mqf6ieV zFx~aPUOJGR=4{KsW7I?<=J2|lY`NTU=lt=%JE9H1vBpkcn=uq(q~=?iBt_-r(PLBM zP-0dxljJO>4Wq-;stY)CLB4q`-r*T$!K2o}?E-w_i>3_aEbA^MB7P5piwt1dI-6o!qWCy0 ztYy!x9arGTS?kabkkyv*yxvsPQ7Vx)twkS6z2T@kZ|kb8yjm+^$|sEBmvACeqbz)RmxkkDQX-A*K!YFziuhwb|ym>C$}U|J)4y z$(z#)GH%uV6{ec%Zy~AhK|+GtG8u@c884Nq%w`O^wv2#A(&xH@c5M`Vjk*SR_tJnq z0trB#aY)!EKW_}{#L3lph5ow=@|D5LzJYUFD6 z7XnUeo_V0DVSIKMFD_T0AqAO|#VFDc7c?c-Q%#u00F%!_TW1@JVnsfvm@_9HKWflBOUD~)RL``-!P;(bCON_4eVdduMO>?IrQ__*zE@7(OX zUtfH@AX*53&xJW*Pu9zcqxGiM>xol0I~QL5B%Toog3Jlenc^WbVgeBvV8C8AX^Vj& z^I}H})B=VboO%q1;aU5ACMh{yK4J;xlMc`jCnZR^!~LDs_MP&8;dd@4LDWw~*>#OT zeZHwdQWS!tt5MJQI~cw|Ka^b4c|qyd_ly(+Ql2m&AAw^ zQeSXDOOH!!mAgzAp0z)DD>6Xo``b6QwzUV@w%h}Yo>)a|xRi$jGuHQhJVA%>)PUvK zBQ!l0hq<3VZ*RnrDODP)>&iS^wf64C;MGqDvx>|p;35%6(u+IHoNbK z;Gb;TneFo*`zUKS6kwF*&b!U8e5m4YAo03a_e^!5BP42+r)LFhEy?_7U1IR<; z^0v|DhCYMSj<-;MtY%R@Fg;9Kky^pz_t2nJfKWfh5Eu@_l{^ph%1z{jkg5jQrkvD< z#vdK!nku*RrH~TdN~`wDs;d>XY1PH?O<4^U4lmA|wUW{Crrv#r%N>7k#{Gc44Fr|t z@UZP}Y-TrAmnEZ39A*@6;ccsR>)$A)S>$-Cj!=x$rz7IvjHIPM(TB+JFf{ehuIvY$ zsDAwREg*%|=>Hw$`us~RP&3{QJg%}RjJKS^mC_!U;E5u>`X`jW$}P`Mf}?7G7FX#{ zE(9u1SO;3q@ZhDL9O({-RD+SqqPX)`0l5IQu4q)49TUTkxR(czeT}4`WV~pV*KY&i zAl3~X%D2cPVD^B43*~&f%+Op)wl<&|D{;=SZwImydWL6@_RJjxP2g)s=dH)u9Npki zs~z9A+3fj0l?yu4N0^4aC5x)Osnm0qrhz@?nwG_`h(71P znbIewljU%T*cC=~NJy|)#hT+lx#^5MuDDnkaMb*Efw9eThXo|*WOQzJ*#3dmRWm@! zfuSc@#kY{Um^gBc^_Xdxnl!n&y&}R4yAbK&RMc+P^Ti;YIUh|C+K1|=Z^{nZ}}rxH*v{xR!i%qO~o zTr`WDE@k$M9o0r4YUFFeQO7xCu_Zgy)==;fCJ94M_rLAv&~NhfvcLWCoaGg2ao~3e zBG?Ms9B+efMkp}7BhmISGWmJsKI@a8b}4lLI48oWKY|8?zuuNc$lt5Npr+p7a#sWu zh!@2nnLBVJK!$S~>r2-pN||^w|fY`CT{TFnJy`B|e5;=+_v4l8O-fkN&UQbA4NKTyntd zqK{xEKh}U{NHoQUf!M=2(&w+eef77VtYr;xs%^cPfKLObyOV_9q<(%76-J%vR>w9!us-0c-~Y?_EVS%v!* z15s2s3eTs$Osz$JayyH|5nPAIPEX=U;r&p;K14G<1)bvn@?bM5kC{am|C5%hyxv}a z(DeSKI5ZfZ1*%dl8frIX2?);R^^~LuDOpNpk-2R8U1w92HmG1m&|j&J{EK=|p$;f9 z7Rs5|jr4r8k5El&qcuM+YRlKny%t+1CgqEWO>3;BSRZi(LA3U%Jm{@{y+A+w(gzA< z7dBq6a1sEWa4cD0W7=Ld9z0H7RI^Z7vl(bfA;72j?SWCo`#5mVC$l1Q2--%V)-uN* z9ha*s-AdfbDZ8R8*fpwjzx=WvOtmSzGFjC#X)hD%Caeo^OWjS(3h|d9_*U)l%{Ab8 zfv$yoP{OuUl@$(-sEVNt{*=qi5P=lpxWVuz2?I7Dc%BRc+NGNw+323^ z5BXGfS71oP^%apUo(Y#xkxE)y?>BFzEBZ}UBbr~R4$%b7h3iZu3S(|A;&HqBR{nK& z$;GApNnz=kNO^FL&nYcfpB7Qg;hGJPsCW44CbkG1@l9pn0`~oKy5S777uH)l{irK!ru|X+;4&0D;VE*Ii|<3P zUx#xUqvZT5kVQxsF#~MwKnv7;1pR^0;PW@$@T7I?s`_rD1EGUdSA5Q(C<>5SzE!vw z;{L&kKFM-MO>hy#-8z`sdVx})^(Dc-dw;k-h*9O2_YZw}|9^y-|8RQ`BWJUJL(Cer zP5Z@fNc>pTXABbTRY-B5*MphpZv6#i802giwV&SkFCR zGMETyUm(KJbh+&$8X*RB#+{surjr;8^REEt`2&Dubw3$mx>|~B5IKZJ`s_6fw zKAZx9&PwBqW1Oz0r0A4GtnZd7XTKViX2%kPfv+^X3|_}RrQ2e3l=KG_VyY`H?I5&CS+lAX5HbA%TD9u6&s#v!G> zzW9n4J%d5ye7x0y`*{KZvqyXUfMEE^ZIffzI=Hh|3J}^yx7eL=s+TPH(Q2GT-sJ~3 zI463C{(ag7-hS1ETtU;_&+49ABt5!A7CwLwe z=SoA8mYZIQeU;9txI=zcQVbuO%q@E)JI+6Q!3lMc=Gbj(ASg-{V27u>z2e8n;Nc*pf}AqKz1D>p9G#QA+7mqqrEjGfw+85Uyh!=tTFTv3|O z+)-kFe_8FF_EkTw!YzwK^Hi^_dV5x-Ob*UWmD-})qKj9@aE8g240nUh=g|j28^?v7 zHRTBo{0KGaWBbyX2+lx$wgXW{3aUab6Bhm1G1{jTC7ota*JM6t+qy)c5<@ zpc&(jVdTJf(q3xB=JotgF$X>cxh7k*(T`-V~AR+`%e?YOeALQ2Qud( zz35YizXt(aW3qndR}fTw1p()Ol4t!D1pitGNL95{SX4ywzh0SF;=!wf=?Q?_h6!f* zh7<+GFi)q|XBsvXZ^qVCY$LUa{5?!CgwY?EG;*)0ceFe&=A;!~o`ae}Z+6me#^sv- z1F6=WNd6>M(~ z+092z>?Clrcp)lYNQl9jN-JF6n&Y0mp7|I0dpPx+4*RRK+VQI~>en0Dc;Zfl+x z_e_b7s`t1_A`RP3$H}y7F9_na%D7EM+**G_Z0l_nwE+&d_kc35n$Fxkd4r=ltRZhh zr9zER8>j(EdV&Jgh(+i}ltESBK62m0nGH6tCBr90!4)-`HeBmz54p~QP#dsu%nb~W z7sS|(Iydi>C@6ZM(Us!jyIiszMkd)^u<1D+R@~O>HqZIW&kearPWmT>63%_t2B{_G zX{&a(gOYJx!Hq=!T$RZ&<8LDnxsmx9+TBL0gTk$|vz9O5GkK_Yx+55^R=2g!K}NJ3 zW?C;XQCHZl7H`K5^BF!Q5X2^Mj93&0l_O3Ea3!Ave|ixx+~bS@Iv18v2ctpSt4zO{ zp#7pj!AtDmti$T`e9{s^jf(ku&E|83JIJO5Qo9weT6g?@vX!{7)cNwymo1+u(YQ94 zopuz-L@|5=h8A!(g-MXgLJC0MA|CgQF8qlonnu#j z;uCeq9ny9QSD|p)9sp3ebgY3rk#y0DA(SHdh$DUm^?GI<>%e1?&}w(b zdip1;P2Z=1wM+$q=TgLP$}svd!vk+BZ@h<^4R=GS2+sri7Z*2f`9 z5_?i)xj?m#pSVchk-SR!2&uNhzEi+#5t1Z$o0PoLGz*pT64%+|Wa+rd5Z}60(j?X= z{NLjtgRb|W?CUADqOS@(*MA-l|E342NxRaxLTDqsOyfWWe%N(jjBh}G zm7WPel6jXijaTiNita+z(5GCO0NM=Melxud57PP^d_U## zbA;9iVi<@wr0DGB8=T9Ab#2K_#zi=$igyK48@;V|W`fg~7;+!q8)aCOo{HA@vpSy-4`^!ze6-~8|QE||hC{ICKllG9fbg_Y7v z$jn{00!ob3!@~-Z%!rSZ0JO#@>|3k10mLK0JRKP-Cc8UYFu>z93=Ab-r^oL2 zl`-&VBh#=-?{l1TatC;VweM^=M7-DUE>m+xO7Xi6vTEsReyLs8KJ+2GZ&rxw$d4IT zPXy6pu^4#e;;ZTsgmG+ZPx>piodegkx2n0}SM77+Y*j^~ICvp#2wj^BuqRY*&cjmL zcKp78aZt>e{3YBb4!J_2|K~A`lN=u&5j!byw`1itV(+Q_?RvV7&Z5XS1HF)L2v6ji z&kOEPmv+k_lSXb{$)of~(BkO^py&7oOzpjdG>vI1kcm_oPFHy38%D4&A4h_CSo#lX z2#oqMCTEP7UvUR3mwkPxbl8AMW(e{ARi@HCYLPSHE^L<1I}OgZD{I#YH#GKnpRmW3 z2jkz~Sa(D)f?V?$gNi?6)Y;Sm{&?~2p=0&BUl_(@hYeX8YjaRO=IqO7neK0RsSNdYjD zaw$g2sG(>JR=8Iz1SK4`*kqd_3-?;_BIcaaMd^}<@MYbYisWZm2C2|Np_l|8r9yM|JkUngSo@?wci(7&O9a z%|V(4C1c9pps0xxzPbXH=}QTxc2rr7fXk$9`a6TbWKPCz&p=VsB8^W96W=BsB|7bc zf(QR8&Ktj*iz)wK&mW`#V%4XTM&jWNnDF56O+2bo<3|NyUhQ%#OZE8$Uv2a@J>D%t zMVMiHh?es!Ex19q&6eC&L=XDU_BA&uR^^w>fpz2_`U87q_?N2y;!Z!bjoeKrzfC)} z?m^PM=(z{%n9K`p|7Bz$LuC7!>tFOuN74MFELm}OD9?%jpT>38J;=1Y-VWtZAscaI z_8jUZ#GwWz{JqvGEUmL?G#l5E=*m>`cY?m*XOc*yOCNtpuIGD+Z|kn4Xww=BLrNYS zGO=wQh}Gtr|7DGXLF%|`G>J~l{k^*{;S-Zhq|&HO7rC_r;o`gTB7)uMZ|WWIn@e0( zX$MccUMv3ABg^$%_lNrgU{EVi8O^UyGHPNRt%R!1#MQJn41aD|_93NsBQhP80yP<9 zG4(&0u7AtJJXLPcqzjv`S~5;Q|5TVGccN=Uzm}K{v)?f7W!230C<``9(64}D2raRU zAW5bp%}VEo{4Rko`bD%Ehf=0voW?-4Mk#d3_pXTF!-TyIt6U+({6OXWVAa;s-`Ta5 zTqx&8msH3+DLrVmQOTBOAj=uoxKYT3DS1^zBXM?1W+7gI!aQNPYfUl{3;PzS9*F7g zWJN8x?KjBDx^V&6iCY8o_gslO16=kh(|Gp)kz8qlQ`dzxQv;)V&t+B}wwdi~uBs4? zu~G|}y!`3;8#vIMUdyC7YEx6bb^1o}G!Jky4cN?BV9ejBfN<&!4M)L&lRKiuMS#3} z_B}Nkv+zzxhy{dYCW$oGC&J(Ty&7%=5B$sD0bkuPmj7g>|962`(Q{ZZMDv%YMuT^KweiRDvYTEop3IgFv#)(w>1 zSzH>J`q!LK)c(AK>&Ib)A{g`Fdykxqd`Yq@yB}E{gnQV$K!}RsgMGWqC3DKE(=!{}ekB3+(1?g}xF>^icEJbc z5bdxAPkW90atZT+&*7qoLqL#p=>t-(-lsnl2XMpZcYeW|o|a322&)yO_8p(&Sw{|b zn(tY$xn5yS$DD)UYS%sP?c|z>1dp!QUD)l;aW#`%qMtQJjE!s2z`+bTSZmLK7SvCR z=@I4|U^sCwZLQSfd*ACw9B@`1c1|&i^W_OD(570SDLK`MD0wTiR8|$7+%{cF&){$G zU~|$^Ed?TIxyw{1$e|D$050n8AjJvvOWhLtLHbSB|HIfjMp+gu>DraHZJRrdO53(= z+o-f{+qNog+qSLB%KY;5>Av6X(>-qYk3IIEwZ5~6a+P9lMpC^ z8CJ0q>rEpjlsxCvJm=kms@tlN4+sv}He`xkr`S}bGih4t`+#VEIt{1veE z{ZLtb_pSbcfcYPf4=T1+|BtR!x5|X#x2TZEEkUB6kslKAE;x)*0x~ES0kl4Dex4e- zT2P~|lT^vUnMp{7e4OExfxak0EE$Hcw;D$ehTV4a6hqxru0$|Mo``>*a5=1Ym0u>BDJKO|=TEWJ5jZu!W}t$Kv{1!q`4Sn7 zrxRQOt>^6}Iz@%gA3&=5r;Lp=N@WKW;>O!eGIj#J;&>+3va^~GXRHCY2}*g#9ULab zitCJt-OV0*D_Q3Q`p1_+GbPxRtV_T`jyATjax<;zZ?;S+VD}a(aN7j?4<~>BkHK7bO8_Vqfdq1#W&p~2H z&w-gJB4?;Q&pG9%8P(oOGZ#`!m>qAeE)SeL*t8KL|1oe;#+uOK6w&PqSDhw^9-&Fa zuEzbi!!7|YhlWhqmiUm!muO(F8-F7|r#5lU8d0+=;<`{$mS=AnAo4Zb^{%p}*gZL! zeE!#-zg0FWsSnablw!9$<&K(#z!XOW z;*BVx2_+H#`1b@>RtY@=KqD)63brP+`Cm$L1@ArAddNS1oP8UE$p05R=bvZoYz+^6 z<)!v7pRvi!u_-V?!d}XWQR1~0q(H3{d^4JGa=W#^Z<@TvI6J*lk!A zZ*UIKj*hyO#5akL*Bx6iPKvR3_2-^2mw|Rh-3O_SGN3V9GRo52Q;JnW{iTGqb9W99 z7_+F(Op6>~3P-?Q8LTZ-lwB}xh*@J2Ni5HhUI3`ct|*W#pqb>8i*TXOLn~GlYECIj zhLaa_rBH|1jgi(S%~31Xm{NB!30*mcsF_wgOY2N0XjG_`kFB+uQuJbBm3bIM$qhUyE&$_u$gb zpK_r{99svp3N3p4yHHS=#csK@j9ql*>j0X=+cD2dj<^Wiu@i>c_v zK|ovi7}@4sVB#bzq$n3`EgI?~xDmkCW=2&^tD5RuaSNHf@Y!5C(Is$hd6cuyoK|;d zO}w2AqJPS`Zq+(mc*^%6qe>1d&(n&~()6-ZATASNPsJ|XnxelLkz8r1x@c2XS)R*H(_B=IN>JeQUR;T=i3<^~;$<+8W*eRKWGt7c#>N`@;#!`kZ!P!&{9J1>_g8Zj zXEXxmA=^{8A|3=Au+LfxIWra)4p<}1LYd_$1KI0r3o~s1N(x#QYgvL4#2{z8`=mXy zQD#iJ0itk1d@Iy*DtXw)Wz!H@G2St?QZFz zVPkM%H8Cd2EZS?teQN*Ecnu|PrC!a7F_XX}AzfZl3fXfhBtc2-)zaC2eKx*{XdM~QUo4IwcGgVdW69 z1UrSAqqMALf^2|(I}hgo38l|Ur=-SC*^Bo5ej`hb;C$@3%NFxx5{cxXUMnTyaX{>~ zjL~xm;*`d08bG_K3-E+TI>#oqIN2=An(C6aJ*MrKlxj?-;G zICL$hi>`F%{xd%V{$NhisHSL~R>f!F7AWR&7b~TgLu6!3s#~8|VKIX)KtqTH5aZ8j zY?wY)XH~1_a3&>#j7N}0az+HZ;is;Zw(Am{MX}YhDTe(t{ZZ;TG}2qWYO+hdX}vp9 z@uIRR8g#y~-^E`Qyem(31{H0&V?GLdq9LEOb2(ea#e-$_`5Q{T%E?W(6 z(XbX*Ck%TQM;9V2LL}*Tf`yzai{0@pYMwBu%(I@wTY!;kMrzcfq0w?X`+y@0ah510 zQX5SU(I!*Fag4U6a7Lw%LL;L*PQ}2v2WwYF(lHx_Uz2ceI$mnZ7*eZ?RFO8UvKI0H z9Pq-mB`mEqn6n_W9(s~Jt_D~j!Ln9HA)P;owD-l~9FYszs)oEKShF9Zzcmnb8kZ7% zQ`>}ki1kwUO3j~ zEmh140sOkA9v>j@#56ymn_RnSF`p@9cO1XkQy6_Kog?0ivZDb`QWOX@tjMd@^Qr(p z!sFN=A)QZm!sTh(#q%O{Ovl{IxkF!&+A)w2@50=?a-+VuZt6On1;d4YtUDW{YNDN_ zG@_jZi1IlW8cck{uHg^g=H58lPQ^HwnybWy@@8iw%G! zwB9qVGt_?~M*nFAKd|{cGg+8`+w{j_^;nD>IrPf-S%YjBslSEDxgKH{5p)3LNr!lD z4ii)^%d&cCXIU7UK?^ZQwmD(RCd=?OxmY(Ko#+#CsTLT;p#A%{;t5YpHFWgl+@)N1 zZ5VDyB;+TN+g@u~{UrWrv)&#u~k$S&GeW)G{M#&Di)LdYk?{($Cq zZGMKeYW)aMtjmKgvF0Tg>Mmkf9IB#2tYmH-s%D_9y3{tfFmX1BSMtbe<(yqAyWX60 zzkgSgKb3c{QPG2MalYp`7mIrYg|Y<4Jk?XvJK)?|Ecr+)oNf}XLPuTZK%W>;<|r+% zTNViRI|{sf1v7CsWHvFrkQ$F7+FbqPQ#Bj7XX=#M(a~9^80}~l-DueX#;b}Ajn3VE z{BWI}$q{XcQ3g{(p>IOzFcAMDG0xL)H%wA)<(gl3I-oVhK~u_m=hAr&oeo|4lZbf} z+pe)c34Am<=z@5!2;_lwya;l?xV5&kWe}*5uBvckm(d|7R>&(iJNa6Y05SvlZcWBlE{{%2- z`86)Y5?H!**?{QbzGG~|k2O%eA8q=gxx-3}&Csf6<9BsiXC)T;x4YmbBIkNf;0Nd5 z%whM^!K+9zH>on_<&>Ws?^v-EyNE)}4g$Fk?Z#748e+GFp)QrQQETx@u6(1fk2!(W zWiCF~MomG*y4@Zk;h#2H8S@&@xwBIs|82R*^K(i*0MTE%Rz4rgO&$R zo9Neb;}_ulaCcdn3i17MO3NxzyJ=l;LU*N9ztBJ30j=+?6>N4{9YXg$m=^9@Cl9VY zbo^{yS@gU=)EpQ#;UIQBpf&zfCA;00H-ee=1+TRw@(h%W=)7WYSb5a%$UqNS@oI@= zDrq|+Y9e&SmZrH^iA>Of8(9~Cf-G(P^5Xb%dDgMMIl8gk6zdyh`D3OGNVV4P9X|EvIhplXDld8d z^YWtYUz@tpg*38Xys2?zj$F8%ivA47cGSl;hjD23#*62w3+fwxNE7M7zVK?x_`dBSgPK zWY_~wF~OEZi9|~CSH8}Xi>#8G73!QLCAh58W+KMJJC81{60?&~BM_0t-u|VsPBxn* zW7viEKwBBTsn_A{g@1!wnJ8@&h&d>!qAe+j_$$Vk;OJq`hrjzEE8Wjtm)Z>h=*M25 zOgETOM9-8xuuZ&^@rLObtcz>%iWe%!uGV09nUZ*nxJAY%&KAYGY}U1WChFik7HIw% zZP$3Bx|TG_`~19XV7kfi2GaBEhKap&)Q<9`aPs#^!kMjtPb|+-fX66z3^E)iwyXK7 z8)_p<)O{|i&!qxtgBvWXx8*69WO$5zACl++1qa;)0zlXf`eKWl!0zV&I`8?sG)OD2Vy?reNN<{eK+_ za4M;Hh%&IszR%)&gpgRCP}yheQ+l#AS-GnY81M!kzhWxIR?PW`G3G?} z$d%J28uQIuK@QxzGMKU_;r8P0+oIjM+k)&lZ39i#(ntY)*B$fdJnQ3Hw3Lsi8z&V+ zZly2}(Uzpt2aOubRjttzqrvinBFH4jrN)f0hy)tj4__UTwN)#1fj3-&dC_Vh7}ri* zfJ=oqLMJ-_<#rwVyN}_a-rFBe2>U;;1(7UKH!$L??zTbbzP#bvyg7OQBGQklJ~DgP zd<1?RJ<}8lWwSL)`jM53iG+}y2`_yUvC!JkMpbZyb&50V3sR~u+lok zT0uFRS-yx@8q4fPRZ%KIpLp8R#;2%c&Ra4p(GWRT4)qLaPNxa&?8!LRVdOUZ)2vrh zBSx&kB%#Y4!+>~)<&c>D$O}!$o{<1AB$M7-^`h!eW;c(3J~ztoOgy6Ek8Pwu5Y`Xion zFl9fb!k2`3uHPAbd(D^IZmwR5d8D$495nN2`Ue&`W;M-nlb8T-OVKt|fHk zBpjX$a(IR6*-swdNk@#}G?k6F-~c{AE0EWoZ?H|ZpkBxqU<0NUtvubJtwJ1mHV%9v?GdDw; zAyXZiD}f0Zdt-cl9(P1la+vQ$Er0~v}gYJVwQazv zH#+Z%2CIfOf90fNMGos|{zf&N`c0@x0N`tkFv|_9af3~<0z@mnf*e;%r*Fbuwl-IW z{}B3=(mJ#iwLIPiUP`J3SoP~#)6v;aRXJ)A-pD2?_2_CZ#}SAZ<#v7&Vk6{*i(~|5 z9v^nC`T6o`CN*n%&9+bopj^r|E(|pul;|q6m7Tx+U|UMjWK8o-lBSgc3ZF=rP{|l9 zc&R$4+-UG6i}c==!;I#8aDIbAvgLuB66CQLRoTMu~jdw`fPlKy@AKYWS-xyZzPg&JRAa@m-H43*+ne!8B7)HkQY4 zIh}NL4Q79a-`x;I_^>s$Z4J4-Ngq=XNWQ>yAUCoe&SMAYowP>r_O}S=V+3=3&(O=h zNJDYNs*R3Y{WLmBHc?mFEeA4`0Y`_CN%?8qbDvG2m}kMAiqCv`_BK z_6a@n`$#w6Csr@e2YsMx8udNWtNt=kcqDZdWZ-lGA$?1PA*f4?X*)hjn{sSo8!bHz zb&lGdAgBx@iTNPK#T_wy`KvOIZvTWqSHb=gWUCKXAiB5ckQI`1KkPx{{%1R*F2)Oc z(9p@yG{fRSWE*M9cdbrO^)8vQ2U`H6M>V$gK*rz!&f%@3t*d-r3mSW>D;wYxOhUul zk~~&ip5B$mZ~-F1orsq<|1bc3Zpw6)Ws5;4)HilsN;1tx;N6)tuePw& z==OlmaN*ybM&-V`yt|;vDz(_+UZ0m&&9#{9O|?0I|4j1YCMW;fXm}YT$0%EZ5^YEI z4i9WV*JBmEU{qz5O{#bs`R1wU%W$qKx?bC|e-iS&d*Qm7S=l~bMT{~m3iZl+PIXq{ zn-c~|l)*|NWLM%ysfTV-oR0AJ3O>=uB-vpld{V|cWFhI~sx>ciV9sPkC*3i0Gg_9G!=4ar*-W?D9)?EFL1=;O+W8}WGdp8TT!Fgv z{HKD`W>t(`Cds_qliEzuE!r{ihwEv1l5o~iqlgjAyGBi)$%zNvl~fSlg@M=C{TE;V zQkH`zS8b&!ut(m)%4n2E6MB>p*4(oV>+PT51#I{OXs9j1vo>9I<4CL1kv1aurV*AFZ^w_qfVL*G2rG@D2 zrs87oV3#mf8^E5hd_b$IXfH6vHe&lm@7On~Nkcq~YtE!}ad~?5*?X*>y`o;6Q9lkk zmf%TYonZM`{vJg$`lt@MXsg%*&zZZ0uUSse8o=!=bfr&DV)9Y6$c!2$NHyYAQf*Rs zk{^?gl9E z5Im8wlAsvQ6C2?DyG@95gUXZ3?pPijug25g;#(esF_~3uCj3~94}b*L>N2GSk%Qst z=w|Z>UX$m!ZOd(xV*2xvWjN&c5BVEdVZ0wvmk)I+YxnyK%l~caR=7uNQ=+cnNTLZ@&M!I$Mj-r{!P=; z`C2)D=VmvK8@T5S9JZoRtN!S*D_oqOxyy!q6Zk|~4aT|*iRN)fL)c>-yycR>-is0X zKrko-iZw(f(!}dEa?hef5yl%p0-v-8#8CX8!W#n2KNyT--^3hq6r&`)5Y@>}e^4h- zlPiDT^zt}Ynk&x@F8R&=)k8j$=N{w9qUcIc&)Qo9u4Y(Ae@9tA`3oglxjj6c{^pN( zQH+Uds2=9WKjH#KBIwrQI%bbs`mP=7V>rs$KG4|}>dxl_k!}3ZSKeEen4Iswt96GGw`E6^5Ov)VyyY}@itlj&sao|>Sb5 zeY+#1EK(}iaYI~EaHQkh7Uh>DnzcfIKv8ygx1Dv`8N8a6m+AcTa-f;17RiEed>?RT zk=dAksmFYPMV1vIS(Qc6tUO+`1jRZ}tcDP? zt)=7B?yK2RcAd1+Y!$K5*ds=SD;EEqCMG6+OqPoj{&8Y5IqP(&@zq@=A7+X|JBRi4 zMv!czlMPz)gt-St2VZwDD=w_S>gRpc-g zUd*J3>bXeZ?Psjohe;z7k|d<*T21PA1i)AOi8iMRwTBSCd0ses{)Q`9o&p9rsKeLaiY zluBw{1r_IFKR76YCAfl&_S1*(yFW8HM^T()&p#6y%{(j7Qu56^ZJx1LnN`-RTwimdnuo*M8N1ISl+$C-%=HLG-s} zc99>IXRG#FEWqSV9@GFW$V8!{>=lSO%v@X*pz*7()xb>=yz{E$3VE;e)_Ok@A*~El zV$sYm=}uNlUxV~6e<6LtYli1!^X!Ii$L~j4e{sI$tq_A(OkGquC$+>Rw3NFObV2Z)3Rt~Jr{oYGnZaFZ^g5TDZlg;gaeIP} z!7;T{(9h7mv{s@piF{-35L=Ea%kOp;^j|b5ZC#xvD^^n#vPH=)lopYz1n?Kt;vZmJ z!FP>Gs7=W{sva+aO9S}jh0vBs+|(B6Jf7t4F^jO3su;M13I{2rd8PJjQe1JyBUJ5v zcT%>D?8^Kp-70bP8*rulxlm)SySQhG$Pz*bo@mb5bvpLAEp${?r^2!Wl*6d7+0Hs_ zGPaC~w0E!bf1qFLDM@}zso7i~(``)H)zRgcExT_2#!YOPtBVN5Hf5~Ll3f~rWZ(UsJtM?O*cA1_W0)&qz%{bDoA}{$S&-r;0iIkIjbY~ zaAqH45I&ALpP=9Vof4OapFB`+_PLDd-0hMqCQq08>6G+C;9R~}Ug_nm?hhdkK$xpI zgXl24{4jq(!gPr2bGtq+hyd3%Fg%nofK`psHMs}EFh@}sdWCd!5NMs)eZg`ZlS#O0 zru6b8#NClS(25tXqnl{|Ax@RvzEG!+esNW-VRxba(f`}hGoqci$U(g30i}2w9`&z= zb8XjQLGN!REzGx)mg~RSBaU{KCPvQx8)|TNf|Oi8KWgv{7^tu}pZq|BS&S<53fC2K4Fw6>M^s$R$}LD*sUxdy6Pf5YKDbVet;P!bw5Al-8I1Nr(`SAubX5^D9hk6$agWpF}T#Bdf{b9-F#2WVO*5N zp+5uGgADy7m!hAcFz{-sS0kM7O)qq*rC!>W@St~^OW@R1wr{ajyYZq5H!T?P0e+)a zaQ%IL@X_`hzp~vRH0yUblo`#g`LMC%9}P;TGt+I7qNcBSe&tLGL4zqZqB!Bfl%SUa z6-J_XLrnm*WA`34&mF+&e1sPCP9=deazrM=Pc4Bn(nV;X%HG^4%Afv4CI~&l!Sjzb z{rHZ3od0!Al{}oBO>F*mOFAJrz>gX-vs!7>+_G%BB(ljWh$252j1h;9p~xVA=9_`P z5KoFiz96_QsTK%B&>MSXEYh`|U5PjX1(+4b#1PufXRJ*uZ*KWdth1<0 zsAmgjT%bowLyNDv7bTUGy|g~N34I-?lqxOUtFpTLSV6?o?<7-UFy*`-BEUsrdANh} zBWkDt2SAcGHRiqz)x!iVoB~&t?$yn6b#T=SP6Ou8lW=B>=>@ik93LaBL56ub`>Uo!>0@O8?e)$t(sgy$I z6tk3nS@yFFBC#aFf?!d_3;%>wHR;A3f2SP?Na8~$r5C1N(>-ME@HOpv4B|Ty7%jAv zR}GJwsiJZ5@H+D$^Cwj#0XA_(m^COZl8y7Vv(k=iav1=%QgBOVzeAiw zaDzzdrxzj%sE^c9_uM5D;$A_7)Ln}BvBx^=)fO+${ou%B*u$(IzVr-gH3=zL6La;G zu0Kzy5CLyNGoKRtK=G0-w|tnwI)puPDOakRzG(}R9fl7#<|oQEX;E#yCWVg95 z;NzWbyF&wGg_k+_4x4=z1GUcn6JrdX4nOVGaAQ8#^Ga>aFvajQN{!+9rgO-dHP zIp@%&ebVg}IqnRWwZRTNxLds+gz2@~VU(HI=?Epw>?yiEdZ>MjajqlO>2KDxA>)cj z2|k%dhh%d8SijIo1~20*5YT1eZTDkN2rc^zWr!2`5}f<2f%M_$to*3?Ok>e9$X>AV z2jYmfAd)s|(h?|B(XYrIfl=Wa_lBvk9R1KaP{90-z{xKi+&8=dI$W0+qzX|ZovWGOotP+vvYR(o=jo?k1=oG?%;pSqxcU* zWVGVMw?z__XQ9mnP!hziHC`ChGD{k#SqEn*ph6l46PZVkm>JF^Q{p&0=MKy_6apts z`}%_y+Tl_dSP(;Ja&sih$>qBH;bG;4;75)jUoVqw^}ee=ciV;0#t09AOhB^Py7`NC z-m+ybq1>_OO+V*Z>dhk}QFKA8V?9Mc4WSpzj{6IWfFpF7l^au#r7&^BK2Ac7vCkCn{m0uuN93Ee&rXfl1NBY4NnO9lFUp zY++C1I;_{#OH#TeP2Dp?l4KOF8ub?m6zE@XOB5Aiu$E~QNBM@;r+A5mF2W1-c7>ex zHiB=WJ&|`6wDq*+xv8UNLVUy4uW1OT>ey~Xgj@MMpS@wQbHAh>ysYvdl-1YH@&+Q! z075(Qd4C!V`9Q9jI4 zSt{HJRvZec>vaL_brKhQQwbpQd4_Lmmr0@1GdUeU-QcC{{8o=@nwwf>+dIKFVzPriGNX4VjHCa zTbL9w{Y2V87c2ofX%`(48A+4~mYTiFFl!e{3K^C_k%{&QTsgOd0*95KmWN)P}m zTRr{`f7@=v#+z_&fKYkQT!mJn{*crj%ZJz#(+c?>cD&2Lo~FFAWy&UG*Op^pV`BR^I|g?T>4l5;b|5OQ@t*?_Slp`*~Y3`&RfKD^1uLezIW(cE-Dq2z%I zBi8bWsz0857`6e!ahet}1>`9cYyIa{pe53Kl?8|Qg2RGrx@AlvG3HAL-^9c^1GW;)vQt8IK+ zM>!IW*~682A~MDlyCukldMd;8P|JCZ&oNL(;HZgJ>ie1PlaInK7C@Jg{3kMKYui?e!b`(&?t6PTb5UPrW-6DVU%^@^E`*y-Fd(p|`+JH&MzfEq;kikdse ziFOiDWH(D< zyV7Rxt^D0_N{v?O53N$a2gu%1pxbeK;&ua`ZkgSic~$+zvt~|1Yb=UfKJW2F7wC^evlPf(*El+#}ZBy0d4kbVJsK- z05>;>?HZO(YBF&v5tNv_WcI@O@LKFl*VO?L(!BAd!KbkVzo;v@~3v`-816GG?P zY+H3ujC>5=Am3RIZDdT#0G5A6xe`vGCNq88ZC1aVXafJkUlcYmHE^+Z{*S->ol%-O znm9R0TYTr2w*N8Vs#s-5=^w*{Y}qp5GG)Yt1oLNsH7y~N@>Eghms|K*Sdt_u!&I}$ z+GSdFTpbz%KH+?B%Ncy;C`uW6oWI46(tk>r|5|-K6)?O0d_neghUUOa9BXHP*>vi; z={&jIGMn-92HvInCMJcyXwHTJ42FZp&Wxu+9Rx;1x(EcIQwPUQ@YEQQ`bbMy4q3hP zNFoq~Qd0=|xS-R}k1Im3;8s{BnS!iaHIMLx)aITl)+)?Yt#fov|Eh>}dv@o6R{tG>uHsy&jGmWN5+*wAik|78(b?jtysPHC#e+Bzz~V zS3eEXv7!Qn4uWi!FS3B?afdD*{fr9>B~&tc671fi--V}~E4un;Q|PzZRwk-azprM$4AesvUb5`S`(5x#5VJ~4%ET6&%GR$}muHV-5lTsCi_R|6KM(g2PCD@|yOpKluT zakH!1V7nKN)?6JmC-zJoA#ciFux8!)ajiY%K#RtEg$gm1#oKUKX_Ms^%hvKWi|B=~ zLbl-L)-=`bfhl`>m!^sRR{}cP`Oim-{7}oz4p@>Y(FF5FUEOfMwO!ft6YytF`iZRq zfFr{!&0Efqa{1k|bZ4KLox;&V@ZW$997;+Ld8Yle91he{BfjRhjFTFv&^YuBr^&Pe zswA|Bn$vtifycN8Lxr`D7!Kygd7CuQyWqf}Q_PM}cX~S1$-6xUD%-jrSi24sBTFNz(Fy{QL2AmNbaVggWOhP;UY4D>S zqKr!UggZ9Pl9Nh_H;qI`-WoH{ceXj?m8y==MGY`AOJ7l0Uu z)>M%?dtaz2rjn1SW3k+p`1vs&lwb%msw8R!5nLS;upDSxViY98IIbxnh{}mRfEp=9 zbrPl>HEJeN7J=KnB6?dwEA6YMs~chHNG?pJsEj#&iUubdf3JJwu=C(t?JpE6xMyhA3e}SRhunDC zn-~83*9=mADUsk^sCc%&&G1q5T^HR9$P#2DejaG`Ui*z1hI#h7dwpIXg)C{8s< z%^#@uQRAg-$z&fmnYc$Duw63_Zopx|n{Bv*9Xau{a)2%?H<6D>kYY7_)e>OFT<6TT z0A}MQLgXbC2uf`;67`mhlcUhtXd)Kbc$PMm=|V}h;*_%vCw4L6r>3Vi)lE5`8hkSg zNGmW-BAOO)(W((6*e_tW&I>Nt9B$xynx|sj^ux~?q?J@F$L4;rnm_xy8E*JYwO-02u9_@@W0_2@?B@1J{y~Q39N3NX^t7#`=34Wh)X~sU&uZWgS1Z09%_k|EjA4w_QqPdY`oIdv$dJZ;(!k)#U8L+|y~gCzn+6WmFt#d{OUuKHqh1-uX_p*Af8pFYkYvKPKBxyid4KHc}H` z*KcyY;=@wzXYR{`d{6RYPhapShXIV?0cg_?ahZ7do)Ot#mxgXYJYx}<%E1pX;zqHd zf!c(onm{~#!O$2`VIXezECAHVd|`vyP)Uyt^-075X@NZDBaQt<>trA3nY-Dayki4S zZ^j6CCmx1r46`4G9794j-WC0&R9(G7kskS>=y${j-2;(BuIZTLDmAyWTG~`0)Bxqk zd{NkDe9ug|ms@0A>JVmB-IDuse9h?z9nw!U6tr7t-Lri5H`?TjpV~8(gZWFq4Vru4 z!86bDB;3lpV%{rZ`3gtmcRH1hjj!loI9jN>6stN6A*ujt!~s!2Q+U1(EFQEQb(h4E z6VKuRouEH`G6+8Qv2C)K@^;ldIuMVXdDDu}-!7FS8~k^&+}e9EXgx~)4V4~o6P^52 z)a|`J-fOirL^oK}tqD@pqBZi_;7N43%{IQ{v&G9^Y^1?SesL`;Z(dt!nn9Oj5Odde%opv&t zxJ><~b#m+^KV&b?R#)fRi;eyqAJ_0(nL*61yPkJGt;gZxSHY#t>ATnEl-E%q$E16% zZdQfvhm5B((y4E3Hk6cBdwGdDy?i5CqBlCVHZr-rI$B#>Tbi4}Gcvyg_~2=6O9D-8 zY2|tKrNzbVR$h57R?Pe+gUU_il}ZaWu|Az#QO@};=|(L-RVf0AIW zq#pO+RfM7tdV`9lI6g;{qABNId`fG%U9Va^ravVT^)CklDcx)YJKeJdGpM{W1v8jg z@&N+mR?BPB=K1}kNwXk_pj44sd>&^;d!Z~P>O78emE@Qp@&8PyB^^4^2f7e)gekMv z2aZNvP@;%i{+_~>jK7*2wQc6nseT^n6St9KG#1~Y@$~zR_=AcO2hF5lCoH|M&c{vR zSp(GRVVl=T*m~dIA;HvYm8HOdCkW&&4M~UDd^H)`p__!4k+6b)yG0Zcek8OLw$C^K z3-BbLiG_%qX|ZYpXJ$(c@aa7b4-*IQkDF}=gZSV`*ljP|5mWuHSCcf$5qqhZTv&P?I$z^>}qP(q!Aku2yA5vu38d8x*q{6-1`%PrE_r0-9Qo?a#7Zbz#iGI7K<(@k^|i4QJ1H z4jx?{rZbgV!me2VT72@nBjucoT zUM9;Y%TCoDop?Q5fEQ35bCYk7!;gH*;t9t-QHLXGmUF;|vm365#X)6b2Njsyf1h9JW#x$;@x5Nx2$K$Z-O3txa%;OEbOn6xBzd4n4v)Va=sj5 z%rb#j7{_??Tjb8(Hac<^&s^V{yO-BL*uSUk2;X4xt%NC8SjO-3?;Lzld{gM5A=9AV z)DBu-Z8rRvXXwSVDH|dL-3FODWhfe1C_iF``F05e{dl(MmS|W%k-j)!7(ARkV?6r~ zF=o42y+VapxdZn;GnzZfGu<6oG-gQ7j7Zvgo7Am@jYxC2FpS@I;Jb%EyaJDBQC(q% zKlZ}TVu!>;i3t~OAgl@QYy1X|T~D{HOyaS*Bh}A}S#a9MYS{XV{R-|niEB*W%GPW! zP^NU(L<}>Uab<;)#H)rYbnqt|dOK(-DCnY==%d~y(1*{D{Eo1cqIV8*iMfx&J*%yh zx=+WHjt0q2m*pLx8=--UqfM6ZWjkev>W-*}_*$Y(bikH`#-Gn#!6_ zIA&kxn;XYI;eN9yvqztK-a113A%97in5CL5Z&#VsQ4=fyf&3MeKu70)(x^z_uw*RG zo2Pv&+81u*DjMO6>Mrr7vKE2CONqR6C0(*;@4FBM;jPIiuTuhQ-0&C)JIzo_k>TaS zN_hB;_G=JJJvGGpB?uGgSeKaix~AkNtYky4P7GDTW6{rW{}V9K)Cn^vBYKe*OmP!; zohJs=l-0sv5&phSCi&8JSrokrKP$LVa!LbtlN#T^cedgH@ijt5T-Acxd9{fQY z4qsg1O{|U5Rzh_j;9QD(g*j+*=xULyi-FY|-mUXl7-2O`TYQny<@jSQ%^ye*VW_N< z4mmvhrDYBJ;QSoPvwgi<`7g*Pwg5ANA8i%Kum;<=i|4lwEdN+`)U3f2%bcRZRK!P z70kd~`b0vX=j20UM5rBO#$V~+grM)WRhmzb15ya^Vba{SlSB4Kn}zf#EmEEhGruj| zBn0T2n9G2_GZXnyHcFkUlzdRZEZ0m&bP-MxNr zd;kl7=@l^9TVrg;Y6J(%!p#NV*Lo}xV^Nz0#B*~XRk0K2hgu5;7R9}O=t+R(r_U%j z$`CgPL|7CPH&1cK5vnBo<1$P{WFp8#YUP%W)rS*a_s8kKE@5zdiAh*cjmLiiKVoWD z!y$@Cc5=Wj^VDr$!04FI#%pu6(a9 zM_FAE+?2tp2<$Sqp5VtADB>yY*cRR+{OeZ5g2zW=`>(tA~*-T)X|ahF{xQmypWp%2X{385+=0S|Jyf`XA-c7wAx`#5n2b-s*R>m zP30qtS8aUXa1%8KT8p{=(yEvm2Gvux5z22;isLuY5kN{IIGwYE1Pj);?AS@ex~FEt zQ`Gc|)o-eOyCams!|F0_;YF$nxcMl^+z0sSs@ry01hpsy3p<|xOliR zr-dxK0`DlAydK!br?|Xi(>buASy4@C8)ccRCJ3w;v&tA1WOCaieifLl#(J% zODPi5fr~ASdz$Hln~PVE6xekE{Xb286t(UtYhDWo8JWN6sNyRVkIvC$unIl8QMe@^ z;1c<0RO5~Jv@@gtDGPDOdqnECOurq@l02NC#N98-suyq_)k(`G=O`dJU8I8LcP!4z z8fkgqViqFbR+3IkwLa)^>Z@O{qxTLU63~^lod{@${q;-l?S|4Tq0)As-Gz!D(*P)Vf6wm6B8GGWi7B)Q^~T?sseZeI+}LyBAG!LRZn_ktDlht1j2ok@ljteyuNUkG67 zipkCx-7k(FZQhYjZ%T9X7`tO99$Wj~K`9r0IkWhPul`Q_t1YnVK=YI1dMc_b!FEU4 zkv=PGf{5$P#w{|m92tfVnsnfd%%KW;1a*cLmga4bSYl^*49M4cs+Fe>P!n=$G6hL6 z>IM&0+c(Nvr0I!5CGx7WK*Z3V^w0+QcF=hU0B4=+;=tn*+XDxKa;NB-z4O~I zf}TSb^Z;L_Og>!D1`;w@zf@GCqCUNY%N?IPmEkTco^}bX~BWM_Hamu05>#B zBh%QfUeHPu`MsYVQQ3hOT;HmP_C|nOl zjluk7vaSICyQ01h`^c)DWp>cxPjGEc6D^~2L79hyK_J#<9H#8o`&XM4=aB`@< z<|1oR6Djf))P1l2C{qSwa4u-&LDG{FLz#ym_@I+vo}D}#%;vNN%& zW&9||THv_^B!1Fo+$3A6hEAed$I-{a^6FVvwMtT~e%*&RvY5mj<@(-{y^xn6ZCYqNK|#v^xbWpy15YL18z#Y&5YwOnd!A*@>k^7CaX0~4*6QB{Bgh$KJqesFc(lSQ{iQAKY%Ge}2CeuFJ{4YmgrP(gpcH zXJQjSH^cw`Z0tV^axT&RkOBP2A~#fvmMFrL&mwdDn<*l3;3A425_lzHL`+6sT9LeY zu@TH0u4tj199jQBzz*~Up5)7=4OP%Ok{rxQYNb!hphAoW-BFJn>O=%ov*$ir?dIx% z56Y`>?(1YQ8Fc(D7pq2`9swz@*RIoTAvMT%CPbt;$P%eG(P%*ZMjklLoXqTE*Jg^T zlEQbMi@_E|ll_>pTJ!(-x41R}4sY<5A2VVQ^#4eE{imHt#NEi+#p#EBC2C=9B4A|n zqe03T*czDqQ-VxZ+jPQG!}!M0SlFm^@wTW?otBZ+q~xkk29u1i7Q|kaJ(9{AiP1`p zbEe5&!>V;1wnQ1-Qpyn2B5!S(lh=38hl6IilCC6n4|yz~q94S9_5+Od*$c)%r|)f~ z;^-lf=6POs>Ur4i-F>-wm;3(v7Y_itzt)*M!b~&oK%;re(p^>zS#QZ+Rt$T#Y%q1{ zx+?@~+FjR1MkGr~N`OYBSsVr}lcBZ+ij!0SY{^w((2&U*M`AcfSV9apro+J{>F&tX zT~e zMvsv$Q)AQl_~);g8OOt4plYESr8}9?T!yO(Wb?b~1n0^xVG;gAP}d}#%^9wqN7~F5 z!jWIpqxZ28LyT|UFH!u?V>F6&Hd~H|<(3w*o{Ps>G|4=z`Ws9oX5~)V=uc?Wmg6y< zJKnB4Opz^9v>vAI)ZLf2$pJdm>ZwOzCX@Yw0;-fqB}Ow+u`wglzwznQAP(xbs`fA7 zylmol=ea)g}&;8;)q0h7>xCJA+01w+RY`x`RO% z9g1`ypy?w-lF8e5xJXS4(I^=k1zA46V)=lkCv?k-3hR9q?oZPzwJl$yOHWeMc9wFuE6;SObNsmC4L6;eWPuAcfHoxd59gD7^Xsb$lS_@xI|S-gb? z*;u@#_|4vo*IUEL2Fxci+@yQY6<&t=oNcWTVtfi1Ltveqijf``a!Do0s5e#BEhn5C zBXCHZJY-?lZAEx>nv3k1lE=AN10vz!hpeUY9gy4Xuy940j#Rq^yH`H0W2SgXtn=X1 zV6cY>fVbQhGwQIaEG!O#p)aE8&{gAS z^oVa-0M`bG`0DE;mV)ATVNrt;?j-o*?Tdl=M&+WrW12B{+5Um)qKHd_HIv@xPE+;& zPI|zXfrErYzDD2mOhtrZLAQ zP#f9e!vqBSyoKZ#{n6R1MAW$n8wH~)P3L~CSeBrk4T0dzIp&g9^(_5zY*7$@l%%nL zG$Z}u8pu^Mw}%{_KDBaDjp$NWes|DGAn~WKg{Msbp*uPiH9V|tJ_pLQROQY?T0Pmt zs4^NBZbn7B^L%o#q!-`*+cicZS9Ycu+m)rDb98CJ+m1u}e5ccKwbc0|q)ICBEnLN# zV)8P1s;r@hE3sG2wID0@`M9XIn~hm+W1(scCZr^Vs)w4PKIW_qasyjbOBC`ixG8K$ z9xu^v(xNy4HV{wu2z-B87XG#yWu~B6@|*X#BhR!_jeF*DG@n_RupAvc{DsC3VCHT# za6Z&9k#<*y?O0UoK3MLlSX6wRh`q&E>DOZTG=zRxj0pR0c3vskjPOqkh9;o>a1>!P zxD|LU0qw6S4~iN8EIM2^$k72(=a6-Tk?%1uSj@0;u$0f*LhC%|mC`m`w#%W)IK zN_UvJkmzdP84ZV7CP|@k>j^ zPa%;PDu1TLyNvLQdo!i1XA|49nN}DuTho6=z>Vfduv@}mpM({Jh289V%W@9opFELb z?R}D#CqVew1@W=XY-SoMNul(J)zX(BFP?#@9x<&R!D1X&d|-P;VS5Gmd?Nvu$eRNM zG;u~o*~9&A2k&w}IX}@x>LMHv`ith+t6`uQGZP8JyVimg>d}n$0dDw$Av{?qU=vRq zU@e2worL8vTFtK@%pdbaGdUK*BEe$XE=pYxE_q{(hUR_Gzkn=c#==}ZS^C6fKBIfG z@hc);p+atn`3yrTY^x+<y`F0>p02jUL8cgLa|&yknDj;g73m&Sm&@ju91?uG*w?^d%Yap&d2Bp3v7KlQmh z(N<38o-iRk9*UV?wFirV>|46JqxOZ_o8xv_eJ1dv} zw&zDHZOU%`U{9ckU8DS$lB6J!B`JuThCnwKphODv`3bd?_=~tjNHstM>xoA53-p#F zLCVB^E`@r_D>yHLr10Sm4NRX8FQ+&zw)wt)VsPmLK|vLwB-}}jwEIE!5fLE;(~|DA ztMr8D0w^FPKp{trPYHXI7-;UJf;2+DOpHt%*qRgdWawy1qdsj%#7|aRSfRmaT=a1> zJ8U>fcn-W$l-~R3oikH+W$kRR&a$L!*HdKD_g}2eu*3p)twz`D+NbtVCD|-IQdJlFnZ0%@=!g`nRA(f!)EnC0 zm+420FOSRm?OJ;~8D2w5HD2m8iH|diz%%gCWR|EjYI^n7vRN@vcBrsyQ;zha15{uh zJ^HJ`lo+k&C~bcjhccoiB77-5=SS%s7UC*H!clrU$4QY@aPf<9 z0JGDeI(6S%|K-f@U#%SP`{>6NKP~I#&rSHBTUUvHn#ul4*A@BcRR`#yL%yfZj*$_% zAa$P%`!8xJp+N-Zy|yRT$gj#4->h+eV)-R6l}+)9_3lq*A6)zZ)bnogF9`5o!)ub3 zxCx|7GPCqJlnRVPb&!227Ok@-5N2Y6^j#uF6ihXjTRfbf&ZOP zVc$!`$ns;pPW_=n|8Kw4*2&qx+WMb9!DQ7lC1f@DZyr|zeQcC|B6ma*0}X%BSmFJ6 zeDNWGf=Pmmw5b{1)OZ6^CMK$kw2z*fqN+oup2J8E^)mHj?>nWhBIN|hm#Km4eMyL= zXRqzro9k7(ulJi5J^<`KHJAh-(@W=5x>9+YMFcx$6A5dP-5i6u!k*o-zD z37IkyZqjlNh*%-)rAQrCjJo)u9Hf9Yb1f3-#a=nY&M%a{t0g7w6>{AybZ9IY46i4+%^u zwq}TCN@~S>i7_2T>GdvrCkf&=-OvQV9V3$RR_Gk7$t}63L}Y6d_4l{3b#f9vup-7s z3yKz5)54OVLzH~Ty=HwVC=c$Tl=cvi1L?R>*#ki4t6pgqdB$sx6O(IIvYO8Q>&kq;c3Y-T?b z*6XAc?orv>?V7#vxmD7geKjf%v~%yjbp%^`%e>dw96!JAm4ybAJLo0+4=TB% zShgMl)@@lgdotD?C1Ok^o&hFRYfMbmlbfk677k%%Qy-BG3V9txEjZmK+QY5nlL2D$Wq~04&rwN`-ujpp)wUm5YQc}&tK#zUR zW?HbbHFfSDsT{Xh&RoKiGp)7WPX4 zD^3(}^!TS|hm?YC16YV59v9ir>ypihBLmr?LAY87PIHgRv*SS>FqZwNJKgf6hy8?9 zaGTxa*_r`ZhE|U9S*pn5Mngb7&%!as3%^ifE@zDvX`GP+=oz@p)rAl2KL}ZO1!-us zY`+7ln`|c!2=?tVsO{C}=``aibcdc1N#;c^$BfJr84=5DCy+OT4AB1BUWkDw1R$=FneVh*ajD&(j2IcWH8stMShVcMe zAi6d7p)>hgPJbcb(=NMw$Bo;gQ}3=hCQsi{6{2s~=ZEOizY(j{zYY-W8RiNjycv00 z8(JpE{}=CHx0ib3(nZgo776X=wBUbfk$y2r*}aNG@A0_zOa4k3?1EeH7Z43{@IP>{^M+M`M)0w*@Go z>kg~UfgP1{vH+IU(0p(VRVlLNMHN1C&3cFnp*}4d1a*kwHJL)rjf`Fi5z)#RGTr7E zOhWfTtQyCo&8_N(zIYEugQI}_k|2X(=dMA43Nt*e93&otv`ha-i;ACB$tIK% zRDOtU^1CD5>7?&Vbh<+cz)(CBM}@a)qZ^ld?uYfp3OjiZOCP7u6~H# zMU;=U=1&DQ9Qp|7j4qpN5Dr7sH(p^&Sqy|{uH)lIv3wk?xoVuN`ILg}HUCLs1Bp2^ za8&M?ZQVWFX>Rg4_i$C$U`89i6O(RmWQ4&O=?B6@6`a8fI)Q6q0t{&o%)|n7jN)7V z{S;u+{UzXnUJN}bCE&4u5wBxaFv7De0huAjhy#o~6NH&1X{OA4Y>v0$F-G*gZqFym zhTZ7~nfaMdN8I&2ri;fk*`LhES$vkyq-dBuRF!BC)q%;lt0`Z(*=Sl>uvU`LAvbyt zL1|M@Jas<@1hK!prK}$@&fbf70o7>3&CovCKi815v$6T7R&1GOG~R4pEu2B z%bxG{n`u$7ps(}Tt(P608J@{+>X(?=-j8CkF!T79c`1@E%?vOL%TYrMe1ozi<##IsIC1YRojP!gD%|+7|z^-Vj$a85gbmtB#unyoy%gw9m1yB z|L^-wylT%}=pNpq!QYz9zoV7>zM2g2d9lm{Q zP|dx3=De3NSNGuMWRdO_ctQJUud?_96HbrHiSKmp;{MHZhX#*L+^I11#r;grJ8_21 zt6b*wmCaAw(>A`ftjlL@vi06Z7xF<&xNOrTHrDeMHk*$$+pGK0p+|}H=Kgl{=naBy zclyQsRTraO4!uo})OTSp_x`^0jj7>|H=FOGnAbKT_LuSUiSd3QuCMq>sEhB=V63Nm zZxrtB0)U@x2A#VHqo2ab=pn~tu>kJ;TVASb_&ePAgVcic@>^YM?^LYRLr^O12>~45 z-EE?-Z$xjxsN92EaBi)~D~1OzRVH`o!)kYv7IIx??(B)>R|xa&(wmlU2gdV0+N+3% z7r$w5(L<|?@46ITJZS5koAELgVV_&KHj(9KG??A);@gL`s1th*c#t5>U(*+nb0+H% zOhJG5tth59%*>S~JIi%<0VAi;k>}&(Ojg!fyH0(fza!1kA~a}Vt{|3z{`Pt@VuYyB zFUt(kR$<`X_J&UQ%;ui2zob1!H{PL8X>>wbpGn~@&h__AfBit)4`D^#->1+Qn^MH9 zYD?%)Pa)D-xQzVGm!g)N$^_z`9)(>)gyQ+(7N@k4GO?~43wcE-|77;CPwPXHQcfcJ^I&IOOah zzL|dhoR*#m5sw{b&L=@<-30s9F|{@V05;4Wf6Z_1gpZnJ*SVN}3O7)-=yYuj2)O0d zX=I9TzzTK%QG&ujvS!F*aJ8eqt4|#VE;``yKqCx7#8QC7AmVn+zW9km3L5TN=R>{5 zLcW`6NKkTz`c{`-w!X9zMG;JZP|skLGs7qBHaWj7Ew!VR=`>n30NX)7j~-RbDmQ6b zHr)zVcn^~e2xqFCBG4P$ZCcRDml-&1^5fqN=CHgBVu1yTg32_N>tZ;N%h*TwOf^1lE#w1$yF$kXaP|V$2XuZ+3wH4Ws6%U;^iP|c6`#etHogQ+E@+~PZ1zdGAty6qTmBM z>!)Wfgq~%lD)m>avXMm)ReN}s9!T_>ic6xA|m7$(&n(Z&j} zHC=}~I(^-*PS2pc7%>)6w}F1il&p*0jX1z)jSvG%S{I3d9w$A|5;TS)4w81yzq5f8 zZVfF~`74m1KXQg|`OS>;FCgZw!AL;2PV{&8%~rG!;`eD=g!luE0k40GjIgjD!JSDNf$eW zZtPMF)&EH_#?IwVLEx&Tosh9K8Ln4Pb$`j2=><6MAezsQvhP#YNnw&cL>12xf)dPz z1tk;{SH6HDcbV0x(+5=2n;A->&iYDa5Zr9$&j?2iAz-(l1;#Vc3-ULyqRV9d0*psG7QHE! z*J=*^sKK?iTO$g*+j~C?QzzIu`6Z{2N-ANrd5*?o%x& z&WMin)$Wq%G!?{EH(2}A?Wx@ zn8|q7xPad4Gu>l^&SBl|mhUxp;S+Cb125`h5aBz9pM34$7n-GHGx*=yqAphZKkds7 z$=5Jnt*6&8@y80jNXm|>2IR<$D5frk;c2f5zLS5xe*^W>kkZa5R1+Am34;mo{Gr=Z zD=z8fgTHwx%)7hzjOo9*Cogbru8GgDzrE;3y%TR+u`|zz%c0Tyd8;#EQXdr4Rgx(2LPRzVI2FwsbXwnF;DP^fg zdYOd|zU&AqgCJ;R+?oSgEgZM`ZX>7&$A-j2m|Tcz4ictXoQkz6Tr<2zhOudU16k<7 zLdk&FCL>=a^>0gV@m#9SnMd)R$5&1mh8p2McnUbk;1|C;`7pPkYjf|o>|a6`x`z1O zt>8~Q%zHX%C=D2!;_1eo3qfbB4QQK^{ON_f*7XhLk{6sr2(KIVmax}fUtF-zHZiUd zHPb9jidV`dE;lsw?1uQH!b%MvPE|lh9-8R_z4^PC8{XAf?S73(n*FvYPoMES+LfOx zcjm4ZZOmKY>M2e${QBVT+XnBQ(oC0fAYcXi7+=}_!hS9m>Y%G@zxn3z#Pb;bJ~-kI zAHNmWgQJp$e8L-uKQ|c4B;#0BTsfRB+}pl7xe=2_1U7pahx5S$TVbRnU0oi1?Wh|A zR7ebg9TK1GgKa4@ic#q_*<;c8?CkjX zMMyq`J()_&(j-FZY7q%z6CN^a0%V{UL)jmrvEg{doZd?qIjgJ^UPr(QUs`68;qkdI zzj_XBQ|#K2U!5?fmIEtXX6^rFY;h4=Vx<-C(d;W6Bi_Xsg{ZJPL*K;I?5U$=V-BNP zn9pKiMc=hZNe**GZBw1kVs#-8c2ZRjol}}^V@^}BqY7c0=!mA;v0`d|(d;R-iT|GK z>zt>Tt3oV09%Y;^RM6=p9C-ys_a``HB_D-pnyX(CeA(GiJqx7xxFE52Y`j~iMv;sP z%jPmx#8p%5`flAU(b!c9XBvV+fygn`BP-C#lyRa;9%>YyW6~A_g?@2J+oY0HAg{qO znT4%ViCgw&eE=W8yt-0{cw`tMieWOG3wyNX#3a^qPhE8TH1?QhwhR~}Ic zZ^q$TF8$p0b0=L8aw&qaTjuAYPmr-6x;U*k*vRnOaBwb_( z5+ls5b(E!(71*l)M&(7ZEgBCtB{6Kh#ArV4u0iNnK!ml!nK5=3;9e76yD9oU4xTAK zPGsGkjtFMMY3pRP5u07;#af?b0C7u) zD^=9X@DRasHaf#c>4rF5GAT!Ggj0!7!z?Q-1_X6ZP2g|+?nVutp|rp}eFlKc8}Q&_ z17$NpDQvQolMWZfj0W0|WKm`nd_KXYH_#wRRzs1aRBYqo#feM}a?joONn30Z4Z9PG zg1c!_<52-9D53Wq4z8pUzGkEFm1@Ws(kp4}CO7csZ-7+b)^)M)(xo}_IpTLl7}5BmbBCI{4>rw>4c_gBQHtRd5Z=SW&6Qp2qMOjr3W+ZRmP;S(U+h=^BHKohhRp6Zgf zwt&$zQXhMm@kh1@SB%dIE*kFDZym3Mky$NRljX?}&JGK`PIV1C;Pf!JV{hb4y;Ju- zlpfEPUd+mV5XQH<#BRFhZ}>b#IdF?a?x;rBg-v)@fZpA?+J{3WZjbl3E zv(a&1=pGYPxP@K!6Qg5Vx=-jwc=BA{xL3+QWb&9~DGS1EFkIC+>55{dvY4LV@s5$C zKJmCjigp7?m27*GN_GROz}y+y5%iIj=*JTYccaFjvD&VN%ewfSp=0P zspdFfDqj?gs!N64cEy5uR~wD>af!1PE*xo{^a^8BPIL2=U>B!m2AM0Jf<8qWLoHxi zxQfkbbwkRXgJgLW_j{ZkCxHLBU{@D6T5u90UNs5P769Zei|C$@nA5$L$4ZvxQl1i? z8vLHg17}e{zM$=&h%8Swbfz7yw~X^N|7Chp1bC(oV72l#R8&%Ne5>F=7wR(dB; zkDX!%&fxS19JBjP<6H7+!dO`nPLvB~xn{aDh#^iHKP|A5UQlCG%v%x9@q1w2fa#&% za^UwHu!~(qrv99G%9_e4OBbJ-CkB*1M_?t6UXZ#}4JFDzB|x(1Z}ckuiY}${zj`eVo})!rN8Je z%h2CVJG1$K$2deXx^h8trLs~Han^e>_-M6@0o4C7d548|#mKtm@DvdVAX5ZzA8=*! zKq5C+cM9u)qJ%YBJ1UAcG}6Ji4=$piaZ(K@>1BiD;$R9bR*QP`dH2T=)dgW#f7U)S zZ~i#VYLOnUZt^~Iu3x8QPJaHVUxtRyipQ+tbmWKl14iW1!f6JSDvT$xt8>~7-1ZlJ zU|)Ab*lhvz-JO!$a}RBH9u8$=R)*qeD@iS@(px~OVvML-qqO5&Ujnhw1>G~**Ld{W zE+7h|!{rDZ#;ipZx4^Tcr9vnO)0>WFPzpFu*MYST(`GFzCq*@Gqse6VwDH#x?-{rs z+=dqd$W0*AuAEhzM@GC&!oZa1*lRsx>>mP>DNYigdm^A~xzo}=uV$w#iadO+!&q_~ zT>AsHXOEGsNyfcJt2V$rhGxaIcTEvZr7CMVEu=>l30N~52^71U^<_uw6h@v@`BA2! z)ViU+wF#^$=5o44TpOj?#eyq*+A&c0ghrt8%}SiK)FgLk-;-^+ zXt|1}1vcKAAuR|?L*a8;04p%!M~U2~UC-OJK)DMtBQ#+ZttJgDFNA4zchA*T)cN(E zmpIMLU*c*NrCSV^qdLXD751DsO`#V#K1BVX4qI-B3Rg(zcvlg^mgY^V3Q*5RRQ4-8 z_kAlUisma2SNEx47euK5Y#eu_-gwRW0}M90hEI}eIJ9aU?t11^jSCn4>e~XLSF7Y3 z7JF)1ZbS_P<$<#y(*u@w!jF4FW_f~bxzi%cgP~B1K5N6GFYSAf=D_s5XomU0G9I%Y zPWc{&MItPR#^Le)?zsRkQMmHx^Cnn&;TrPzRVG`wyNH*U;|r3^2NY(z0lwikP}cWF z`p%R@?dy*7H~0&3ST>L9)b7#kwg+|n0#E&-FNf+Z_t7tpa711FogBPV`S3MW_FMGQ zJ@8Z}qXR4-l%p76mvcH`{Fu(^O;8H2@#LZUH#9p6!EX$AEYV$c`s zkPimL3kv>y=WQ+?KIAuim``%cAeBhA6g8}p_*FBH(#{vKi)CIz_D)DFXPql*ccC}O zRW;+Y6V@=&*d6QJUbRxPX+-_24tc-hYHEFaP-IAj*|-P5%xbWujQvu#TF>xigr_r! znuu7b(!PyYX=O#>;+0cGRx>Sy39(3y=TCf_BZ$<%m#inup$>o(3dA1Byfsip8S975-iVe7UklFm|$4&kaJ!n66_k-7-k}Z_?){LQe&wTeJ^CR{u6p+U#4_iSZZ1wjB-1gVGNQqnkk*-wFLj(eK8Ut{waU zb1jwb2I?Wg&98jSQWom8c?2>BWt*!3WQ?>fB$KguB9_sStno%x=JXPEFrT|hh~Po2 zSPzu3IL10O?9U(3{X8OLN-!l6DJVtgr$yYXeAPh~%(FECDe;$mIY7R4Miv1GEFk9x zpw`}E5M)qTr60D^;a#OCd0xP*w8y+my1^l8Qd*V`wLoj)GFFj;;esW2PMO=sbas{yX6asXIJ$|LW< zts$A+JaxoM({kv+2d@#bhl?#V#FZn_=8tTTvup?Vq!p!46W{be)EP=VlYE|UzAU}) zz})UzJVWi;9br0k&5>}sqwa_`TP*c}^$9+q)Dks#qEVg>p)71sqKF-YLP@UF{(>lp7;CHAWK;K0TZ_+?>EtZKprfU@;52a1IU8HNx-mnoZrb8| zP8FPb#T$0VE+G-l508;d{DSfC6#dbp(j|^i^I3z9?Qmkr+(dw^w??h}WTN{_ls-GuE~lF;1Urgbtq|Ud_r>wecb@?{{z? zX>X$&Ud+(I(5}5d^>&Z2m+qy=h#vR*lS084ATwUWZLg6PX1Ft+YI`0iI)ynij}{4X zrQE!Mr1m^-?kw<|VT0mG+5J{!;j;zJT`?_=P*09n+=e``CN|7rC$u~Ksg7LSMS(Q~ z51!n1htcK0q7*K-*u0?c8ZlvPXcNwXmFe0Or2}}R@?j@{ECCNZ6va1tZ>|ZOgGZ1j z9?mRkeSK%{X4O>J$@hyFsD)7s67Uldb>O93wQQiV%-FfbEY_@q>1VUstIJs|QgB`o1z**F#s z^joAYN~5{EQ_wZ~R6-nEV#HsQbNU59dT;G zovb$}pb=LdR^{W2Nh~8yWfq*vC_DvJxM=)2N`5x+N6Sl`3{Wl@$*BYol#0^idTuM` zJ=prt$REkxn6%dimg%99{(Dt6D67sTUR6l1F@9&Z9<)XgWK#x zVohUH6>_xRuw1^V**+BCZ@dZj97T*67OBO>6UUivH`<@ray~ym^E?bO=vKqFfK3Kv z`RKxs4raHacB<(XAeH`@0G*K2@ill_U@m=icT@F{k1PU3j4VBde`ThtW8%Z~A>)45ARjQCDXbH}_rS^IxHGp#utBEj3W3KSAU+$6I4s~9OWueETo!J-f~+DV8< z+VMtdcQ?M+?S}kl&uImYiIUJ-K0-te7W4sdWpS6Fqs-I!Tj{8Qp6lMn$Zm8uU)s{X z8|O}HN%8sEl4em&qv{VBq{}$@cCG{B z5~3DY$WRYSkO~z=sxRct5^G5bPZW;LF)(zY)HREgpRrkYV@H3^BTD6u+bJE~$cqr< zw@Gb3^|n*kHZ%Vnu6~B7pB4iM0C4kDuk8Q1R^<(x%>|sCOl%CTe^N)K?Tiepg?|#m z94!og0*38u|67h%*!)SJhUdvFimsktaqp#im9IpH-$fQc79gi259qPkEZ)XU?2uWW zRg?$8`vl;V%-Tk+rwpTGaxy)h%3AmF^78<#i+Q6~M4#>J4`NNEEzy~xZ&O*9q%}@7 zs9XBO#vSKSM<-OjPIDzO9JiAYFWrK14Am{uZT=S3zaCu~K%kZo&u*=k9L#xi6vyaG zQFD76MOE&=c1G;7Zivp<%%fRq+@3wgZg>k@AYQf|*Qyzy$tqc20m?F5nGbG@V#gW` z8RMb2oBxgiqa?)_G6&-;L#(HCoaJrs_ED{IUZ^$~)+e#0iZT!AJDb2V{Sen*70TO& zyI`*~#ZdLFhYP_#DTuoqQ0OS6j0o15r{}O&YoT5wCp|x_dD{#Y;Y}0P1ta?2VEh4* ztrRN5tL6UvoH@M9L z=%FKpf@iSp2P>C(*o<-Ng4qF#A?i!AxjXLG8%Gm`$rZxw;ZqSvv5@@sZ|N*~do5fb zKWR)T_>`kxaS|MHFh`-`fc`C%=i@EFk$O&)*_OVrgP4MWsZkE2RJB(WC>w}him zb3KV>1I&nHP9};o8Kw-K$wF8`(R?UMzNB22kSIn#dEe|V-CuMw8I7|#`qSB6dpYg$ zoaDHj%zV6*;`u`VVdsTBKv&g75Q`68rdQU6O>_wkMT9d!z@)q2E)R3(j$*C4jp$Fo z2pE>*ih{4Xzh}W+5!Qw)#M*^E(0X-6-!%wj@4*^)8F=N*0Y5Or+>d= zhMNs@R~>R9;KmyP@I@bpU3&w?)jj0rGrb@q)P>wLVbz1!TZY$#+H-mK6B^0{vdvt0 zaJ0~7p%I#1PpPm1DvBzh7*UsCl^I5^`@XzPzbg+v3T_WyKN?TJ9J=57v^IUO`aQN} z@>Y>WIj+gT@-sobU-tW%L5GP(qY?Eep&I;@osY}O*3i1Ar?Sv|EI6S-pK_!~*A$K| zs-hHESqd`vv;zIzgv2ho5-hsIL5Ke~siJ(v0`Qm7W_Rms2rB67=p&HGRhA-)$p-BS zvXSmgGIGgeJMBcsgp=L8U3Ep$VPBFhvJ!3M5{pocGBS~iZj0({9Jt9nbC{Z$LVb%= zGqzRBjlqkAU{#sOX56})^QjX;jQ26M`poAFIZ#H31td9sQlgBBrfIYgDC9+kO~}s{ zb1i*{#{5tPWhv4pecAZygXG>?5xKx7iPXd?nR;QaIfhlhqNBaLDy>9Yd1Sf3P!s4~ zhfHaFGsIFy&ZM=6^qc>>V>o!zk%5Lk5BtS7oU=YfjWUN;c zrh$6Cyr%KC@QNTzTZvb)QXQkV)01MEY+EzC%CJx)Q&6MM={paB}Dp=qCn^eJ}5LeXG9Gqynt0ir>DvSIZ=i?*_xR3=% zppf1w51ypF2KL6ug zCm}eCi>&>xT;Idzh^PmtDWrU(&eC2hAt(nmd#?;W)*&4lb2Z2Ykv*XLNDEm`_1n3C z`l!wZwiF9b?mN@z?s~>v%hT01C{E3md6M5_Xi3fKD6s26Tt~Z>8|~Ao9ds!cF_Y1| zRG>!=TD0k0`|T*)oX!SlSt8g4Uh@nc(QosCoen@i*ZCSyh|IliliuhEw$8?4ZL9N2 zMQ%%S=3Tj_QilhHW@cSr1UYTtDem{A-ZxyCa$K9A%(!`X_?ieJzXbfERST|JxqmbL zHe!hSqYk|!=!$8CJ5>q}Pj63@Q#PO{gpVb+0-qHFM`j5x_s#~dxvy5u62vywq8upP z_)N)3n9cn7YEf2D8L}x0#_B_~>HT8;;8JC5q+}1gEyd%XqYvY?deQzwD1Lx{ghI3; zv?f;&6CY$H&dDL$k#)hb)5lIqUZ~oU!z)hMI!B9THhw?9!}ykqpFJ|hB?JjV9uwqb z3_70pMV^C7I<3Cg&yMi8JJ3V2gYTOMV=IopfZ#1o>&+j-mB-V${Ok(f?I3{+vR~zE_RR$?9xI~^% z53~ z&bCl+6UeKkUWJ-%mnK{9K>?(3BM3C`@xi}v8)q#;YJhMr5dWvMtAL7X``!bHv~(%m zH8d#Q4N6G~lEW}aGn9ZZNT?v9bV$emf)dg#ASDV?(nu+wpu!_X;(vL<<1zBo-~X&N z>keyizVGaP&c65DbIyEwFn2%(L`P424ZI3nFBA%w{yJ?E} zlwSKF;jIhs(!TFOdMUW|(=qHjr#U-k>`>1u1_yL5Gyy;7@WTOt_)nfIp{D9kwR8f0 z;^Fq=iF(&yd|z30&+I`FBM-P6ouHQ@96TkIe@9=pDDL#_zgXos)-ri5lX-&2D~DsI z4R>xVM$c&aFLgFjwq{1I;jpODOx|n*#@e2+Wgdkm(E(Fad_)peD`1^CJ2TpglmgoC)F(Z)F7y2rzzDU^4wvO{bzw{mzSs4tF;*qabKkC?D!j!tbF z4D_6zbqFVI>n@2-Qmg1BiDdD}>E(72)aMv1Y9duOxwlG|E!L(QmQ#j5vmN@a7v{zIt3qQSP?96^$ITE=h~sLn|N|v8YqmA~-0HWgcPHZ@!3Dzm2X{Bozc{qm>J`Ehp}`FQ%Ecbw%+|H8f`pykvo-%&0a z?&ZtJF*{#AYs8Z|z(IFI8sBiZs)L!C9#1W@;hEInZZZdPz2ZnmhoSP9VHQt7mzZUZ zhM!!5IJbe4Z@zEoMjKaxH&Px8p}1<0YmtWwcG@ZPY@*oQSteU zRy+W=Rs>sJ##v^8EJJt0=5---o<@^?fOEp=N<~xXvcf?$gXD0zVHziRMMmC#Mp3o ze(eT!dvjmXp9_C%pV_>{H=nsqYO)n1J?Ihi zjy7f00`|S<;)I!ZyUO{~#+wXX)z(BWsN|$7n9s}H%ZzE8YQv#vRTHjq@D%tYyfe=3)|7jYxRT#E16nFk&1jFC6CH5d4kiJCVq+%r_$Rec7=G!GuZ-0*$5N2GqXB(dqWPS1Um4{xgi2k=;eO_LDy&GR=Q!)bjKY{f!0yoc0Rol&!E`2BkI$5y4U^*k0=GyL-m8XJL%8prM%;fwyX9M^ zs48n3Oh#a>FVWI7dsm~*l0$^J)lxnfTTw~1ceZ73yNvNurwd`;+^1XuucaFN85M8? z$fNl!D9g*O>6IE^POaoDq`86Sw0t4%jIi`&*EEZI?wwOiEvH8(qpfyDvAe`4pWf7k z3-pFgeT{qtj)B!1ZamZ5g3z6Nd40P(%^Kf@#!uzbIk~8w`9wbhWc~1E|sw6-FsOqrhb2DLDwlaq@)Y zAi$KoA=Vyn=Yxqxtf7wu*$47Ht>WZi{AdeN79#9ws~CtE;~gC$q7T>*5yKK3VT)Q=sllRR}lBIGd17+bOu| zeUeUrMgF=Gjk-{epAyUd_KNgwZK_Pz=H$+{4~E_ZRa3IJpU~IZ5U4Z3l%u3{Ls~`H z(iysmm+!HBJTC-$EpHM9yrXUM^_FZ(3sdmsyZ6=lU8bb3V(WK>P0$l~#QA&NMj@OA z*OQ>^-s_D-bda022~!G!bTh7@FR>t!1r`Js1;4$(^_*hH-_pUPf5C}K-v$%i#KBB! zU{~a7)R>ix z#LA|<6v#rwKkB1JBLWkWu#M0#8i1J0e4dFDP3jrlFfxhkDs%Q~)e6e7fR$U?e$<{x zfZb0?UMsB|E}Fk)@|^{)_^L7O%rp1GRNig@bUX(^6}6HoGi8IXoSKpI1A(GV)uA=7 zOXG&KjZYVjYn6}2YV0yfnKsnpDlF)h$Gv--|6$BsWFg|IWnp|#sk}zOAb6Bb?vb@t zs^7=4IdiKE_rUT@rG!D4Zy zcnas#XT77V&%igMXY(lQS|)lgO{pN9!P-94KeZH_+PK5jESYCSPMN)=D(JIAVeB%D zI_>_lvD;pylkZ#Ral0IzC6ei$J$4NnGw(pnVd`&aaNT5mfq-4)aPjj(v;`VvJ6Xxjm@3DX+Kju z@9-h++s7x>idTEL zd)ptYy?P2$S*_DI;eMR0ZdAuS)~fGEZEguO&+3AwW@Sw$&KvgJr6aGK*Ar;0wx`lr z7V&!+9C7`VcV^t+Wj~AweOGQL!)0)serr$8Fez7kC(VSVRdjqpQuq964RW^2euIre zh10&Tv)|dj*CoRozrW<4y_+5}3EGRok+G7ODl3-CF1r?JYDdw&NbcVT=7ljq_K+8bMeG3uRw@3=cof?j+v+WaKI`WqwByf#7aFK3 z0+R34xQ-6nxQ&9xJKl}`C9FlUe1-h^i?5fr5kjot#MA-$%k106t>*gM+yF3m2X#=1tt07`cK)37dA^A4d8%6R>@0U-UZ~wSvzMlK$tlm~aK`%e8|quXyH`aLM0#Dcu%sqEsKV%i zVn_*W-Qbnl)h?RP>)$rZ5JL!*H;Z{ zk7(FB`lo~h&zB|S6j-Na;y$QM*rn^tkO{>#DWZN@IwJps3*Nm&ox0{{;=J~hvPb-* zvAOEPImrdq()yl~`j`Q;R1Y%CdLKKw*;gtNaM~WDO95YXsTjKCOdRD2Is@aVRTYFD zpS=_EB!@Ub&c*JmNMF=F+)Bq)52|=83IEG;M5(Ol*97!W(S-5X-5w&7->`1Pw-0Ml zpA>jaofnyPQTCzoIG}OK9j^nn>F>jC#$iSnJY8y6ue4nxs@3HtfNx01XVK7NcX#Cu z34g-z=0!7ip&@wI>>6ynJYyFTEgH6DA?b>~V%2s_@NPDza5&6cno!S(|85*74}6_M z%s1c4`B{lqMu``(4~Jk#_`^=tu36TgXPv_}{lhhyi(rrSM_uoVVNuZOuxCXom9|wg zNf&BtzX=hVi*4dG&1J!^QW;O%fQ$jVH=W74B8WR)*tM1{(@cHRqiS_W6R^h8uxd@zV>KNI zR(-LNNkLqh>e=CmL|q9sRHm#15%q$o7_GQMp8FLX-HGnJ<+(;k{Q%+Sk+!^mM+2#1y9+gG2IDZGt%;Cfk{+ zT5}^x=!i2$tnH_se6eC zkn;kK>%ICpo=X&=cSsbxQ|AjJ;5Ff;AyIj>$YA8cw*?W^Nn}S|1jrbf@Bd zr82I8KlOh4#5C0sw3oVvuC0NFPKH4S0$~F$U4JM1Im$B%%oGm_5$Lnr{#Pv}eL1k& zMP(pG$MI^8&!nYffq#$zJ^3GF|cC%2d4V@qKV#fu6u2O

k)oKu82Fu=RODzQrHPEC+Mz{hW(G7VuCl8g1ou-Ot!41bp_>OC1&@A_6e*hc)1X zMuDvzEZyB*fW1^+7dL0%ofr;-xT6B@0~|VazatI{60!X=po^uOr6UB$1POKmuI_&b zOL&O+w*!>`k+y%?Z|wm4$@_1|WC|pKM(F{k8TR$-4hs?i|GBc9)qa{vYq)~5qa(2N zsR?s}0Pp^ufVGEB8oE9VCFa0K$x0HSpem!tIyR69y0rnjg8cqjmWyz7*Kx3~X> z|BZX}Y;oVB1HX@l9_-y7dI*WgruY@?rC&64`}3W`ECA>O@Y#Q@JS<4WBF(QbwJqHM zt)fE#6jTSyZ^E8y0INaIf!omWjvS=@15`O%V2CKg+}z=M9##kLKRN0uJuK250bXVU zwzT&n@30^dzKnlL^us;wClg?CKWEtiEb#zhPVx{PxFQiwEPp^C53zN21EdZAz?3D& zC6fK|_!S5Mq&0z;xWGLEv}!zjfpRg_orp7|fXMx=uP!@X`yT@5(N_Hza}p5fBk&|)J7fZ`NQ9Nz@5xT? zi?iV$q+bG!2LZUpF)>Yl!u;DEHV3!i{ipcJm_8Gj@Dac%N3|SQVGqRhrJ;WOR|CtrwzPTW^&$A6!A$E)h7xohm>hA8p{PUZ~ z_&zeg@OL3PxPtzkfsNZAqXCZ8Is7yQ+plm~8;}|~DEkv&f@?q5hB*OGQYXuwVQOp0 z?QQ`6qyp|-$47wjuV74IE_x2I17$+grwMBE^25d<5!lYhnszuh|5Yk;RB+Uk*hk=m zu73=E^7ul{40{A^?Rg^fq0ZfZO@C1HupR*_d;J>lkFv6&x&}4N;t}1T@2}~AC^<3b zA}RxFPPZe5R{_6dIN9N-GT29Oa}RzA2ekKuEVZbuMOB?Xf**`N5&m}?)TjigdY(rF z?~+a=`0);TlDa1j)1G`AfW? zRl883QPq=w zbB|bHEx%_u*$t@Yl#Vc;y*?2W^|^NJ)DmioQFr~1&>MSBL_b(YIpGWdDm3bT=Mgm1 e+h0K+-~H6qzyuy}`;+tYAZFmzUSVSYum1yJqxCBQ diff --git a/Forge/gradle/wrapper/gradle-wrapper.properties b/Forge/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 54f3c9ac..00000000 --- a/Forge/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Tue Sep 03 15:47:51 SAST 2024 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/Forge/gradlew b/Forge/gradlew deleted file mode 100644 index aeb74cbb..00000000 --- a/Forge/gradlew +++ /dev/null @@ -1,245 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/Forge/gradlew.bat b/Forge/gradlew.bat deleted file mode 100644 index 93e3f59f..00000000 --- a/Forge/gradlew.bat +++ /dev/null @@ -1,92 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/Forge/settings.gradle b/Forge/settings.gradle deleted file mode 100644 index ba32d060..00000000 --- a/Forge/settings.gradle +++ /dev/null @@ -1,19 +0,0 @@ -pluginManagement { - repositories { - gradlePluginPortal() - maven { - name = 'MinecraftForge' - url = 'https://maven.minecraftforge.net/' - } - } -} - -buildscript { - repositories { - maven { url = 'https://repo.spongepowered.org/repository/maven-public/' } - } -} - -plugins { - id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0' -} \ No newline at end of file diff --git a/Forge/src/main/java/software/bluelib/BlueLib.java b/Forge/src/main/java/software/bluelib/BlueLib.java deleted file mode 100644 index 1b47ff5e..00000000 --- a/Forge/src/main/java/software/bluelib/BlueLib.java +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (c) BlueLib. Licensed under the MIT License. - -package software.bluelib; - -import net.minecraftforge.common.MinecraftForge; -import net.minecraftforge.eventbus.api.IEventBus; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.DistExecutor; -import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; -import net.minecraftforge.fml.event.lifecycle.FMLLoadCompleteEvent; -import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; -import net.minecraftforge.fml.loading.FMLEnvironment; -import software.bluelib.example.event.ReloadHandler; -import software.bluelib.example.init.ModEntities; -import software.bluelib.example.proxy.ClientProxy; -import software.bluelib.example.proxy.CommonProxy; - -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -/** - * The main class of the {@link BlueLib} mod. - *

- * This class serves as the entry point for the {@link BlueLib} mod, handling initialization by registering event handlers - * and setting up necessary configurations. For more details, refer to the BlueLib Wiki. - *

- * - *

- * Key Methods: - *

    - *
  • {@link #BlueLib()} - Constructs the {@link BlueLib} instance and registers the mod event bus.
  • - *
  • {@link #onLoadComplete(FMLLoadCompleteEvent)} - Handles the event when the mod loading is complete and prints a thank-you message if in developer mode.
  • - *
  • {@link #isDeveloperMode()} - Determines if the mod is running in developer mode.
  • - *
- *

- * - * @see BlueLib Wiki - * @author MeAlam, Dan - * @Co-author All Contributors of BlueLib! - * @since 1.0.0 - */ -@Mod(BlueLib.MODID) -public class BlueLib { - - /** - * A {@link ScheduledExecutorService} used for scheduling tasks, such as printing messages after a delay. - *

- * This is initialized with a single-threaded pool to handle delayed tasks in a separate thread. - *

- * @Co-author MeAlam, Dan - * @since 1.0.0 - */ - private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); - - /** - * The Mod ID for {@link BlueLib}. This serves as a unique identifier for the mod. - * @Co-author MeAlam, Dan - * @since 1.0.0 - */ - public static final String MODID = "bluelib"; - - public static CommonProxy PROXY = DistExecutor.safeRunForDist(() -> ClientProxy::new, () -> CommonProxy::new); - - // public static final Logger LOGGER = LogUtils.getLogger(); - - /** - * Constructs a new {@link BlueLib} instance and registers the mod event bus. - * - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - public BlueLib() - { - IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus(); - modEventBus.register(this); - - if (isDeveloperMode()) { - ModEntities.register(modEventBus); - MinecraftForge.EVENT_BUS.register(ReloadHandler.class); - modEventBus.addListener(this::setupComplete); - modEventBus.addListener(this::setupClient); - } - } - - private void setupClient(final FMLClientSetupEvent event) { - event.enqueueWork(() -> { - PROXY.clientInit(); - }); - } - - private void setupComplete(final FMLLoadCompleteEvent event) { - PROXY.postInit(); - } - - /** - * Handles the {@link FMLLoadCompleteEvent}, which is triggered when the mod loading process is complete. - *

- * If the mod is running in developer mode, it schedules a task to print a thank-you message to the console after a short delay. - *

- * - * @param pEvent {@link FMLLoadCompleteEvent} - The event triggered upon the completion of the mod loading process. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - @SubscribeEvent - public void onLoadComplete(FMLLoadCompleteEvent pEvent) { - if (isDeveloperMode()) { - scheduler.schedule(() -> { - System.out.println(""" - - ************************************************** - * * - * Thank you for using BlueLib! * - * We appreciate your support. * - * * - ************************************************** - """); - scheduler.shutdown(); - }, 3, TimeUnit.SECONDS); - } - } - - /** - * Checks if the mod is running in developer mode. - *

- * Developer mode is determined by checking if the mod is not running in a production environment. - *

- * - * @return {@code true} if the mod is running in developer mode, {@code false} otherwise. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - static boolean isDeveloperMode() { - return !FMLEnvironment.production; - } -} diff --git a/Forge/src/main/java/software/bluelib/entity/variant/VariantLoader.java b/Forge/src/main/java/software/bluelib/entity/variant/VariantLoader.java deleted file mode 100644 index 3dc0034b..00000000 --- a/Forge/src/main/java/software/bluelib/entity/variant/VariantLoader.java +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright (c) BlueLib. Licensed under the MIT License. - -package software.bluelib.entity.variant; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.packs.resources.ResourceManager; -import software.bluelib.interfaces.variant.base.IVariantEntityBase; -import software.bluelib.json.JSONLoader; -import software.bluelib.json.JSONMerger; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * A {@link VariantLoader} class that loads and manages {@link VariantParameter} for entities by merging JSON data from multiple sources. - *

- * Key Methods: - *

    - *
  • {@link #loadVariants(ResourceLocation, ResourceLocation, MinecraftServer, String)} - Loads and merges variant data from both the main mod and the latest datapack.
  • - *
  • {@link #getVariantsFromEntity(String)} - Retrieves the list of loaded {@link VariantParameter} for a specific entity.
  • - *
  • {@link #getVariantByName(String, String)} - Retrieves a specific {@link VariantParameter} by its name for a given entity.
  • - *
- * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ -public class VariantLoader implements IVariantEntityBase { - - /** - * A {@link Map} to store loaded {@link VariantParameter} for each entity type. - * @Co-author MeAlam, Dan - * @since 1.0.0 - */ - private static final Map> entityVariantsMap = new HashMap<>(); - - /** - * A {@link JSONLoader} instance to load JSON data. - * @Co-author MeAlam, Dan - * @since 1.0.0 - */ - private static final JSONLoader jsonLoader = new JSONLoader(); - - /** - * A {@link JSONMerger} instance to merge JSON data. - * @Co-author MeAlam, Dan - * @since 1.0.0 - */ - private static final JSONMerger jsonMerger = new JSONMerger(); - - /** - * A {@code void} method that loads and merges variant data from both the Main Mod and the Latest Datapack. - * Parses the merged data into {@link VariantParameter}. - * - * @param pJSONLocationMod {@link ResourceLocation} - The {@link ResourceLocation} of the Mod's JSON data. - * @param pJSONLocationData {@link ResourceLocation} - The {@link ResourceLocation} of the Latest DataPack's JSON data. - * @param pServer {@link MinecraftServer} - The {@link MinecraftServer} instance. - * @param pEntityName {@link String} - The name of the entity whose variants should be cleared before loading new ones. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - public static void loadVariants(ResourceLocation pJSONLocationMod, ResourceLocation pJSONLocationData, MinecraftServer pServer, String pEntityName) { - clearVariantsForEntity(pEntityName); - ResourceManager resourceManager = pServer.getResourceManager(); - JsonObject mergedJsonObject = new JsonObject(); - - JsonObject modJson = jsonLoader.loadJson(pJSONLocationMod, resourceManager); - JsonObject dataJson = jsonLoader.loadJson(pJSONLocationData, resourceManager); - - jsonMerger.mergeJsonObjects(mergedJsonObject, modJson); - jsonMerger.mergeJsonObjects(mergedJsonObject, dataJson); - - parseVariants(mergedJsonObject); - } - - /** - * A {@code void} method that clears variants for a specific entity type from the map. - * - * @param pEntityName {@link String} - The name of the entity whose variants should be cleared. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - private static void clearVariantsForEntity(String pEntityName) { - entityVariantsMap.remove(pEntityName); - } - - /** - * A {@code void} method that parses the merged JSON data and converts it into {@link VariantParameter} instances. - * - * @param pJsonObject {@link JsonObject} - The merged {@link JsonObject} containing variant data. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - private static void parseVariants(JsonObject pJsonObject) { - for (Map.Entry entry : pJsonObject.entrySet()) { - String entityName = entry.getKey(); - JsonArray textureArray = entry.getValue().getAsJsonArray(); - - List variantList = entityVariantsMap.computeIfAbsent(entityName, k -> new ArrayList<>()); - - for (JsonElement variant : textureArray) { - VariantParameter newVariant = getEntityVariant(entityName, variant.getAsJsonObject()); - - boolean variantExists = variantList.stream() - .anyMatch(v -> v.equals(newVariant)); - - if (!variantExists) { - variantList.add(newVariant); - } - } - } - } - - /** - * A {@link VariantParameter} method that creates a new {@link VariantParameter} instance from a JSON object. - * - * @param pJsonKey {@link String} - The key associated with this variant. - * @param pJsonObject {@link JsonObject} - The {@link JsonObject} containing the variant data. - * @return A {@link VariantParameter} instance. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - private static VariantParameter getEntityVariant(String pJsonKey, JsonObject pJsonObject) { - return new VariantParameter(pJsonKey, pJsonObject); - } - - /** - * A {@link List} method that retrieves the {@link List} of loaded {@link VariantParameter} - * for a specific entity. - * - * @param pEntityName {@link String} - The name of the entity to retrieve variants for. - * @return A {@link List} of {@link VariantParameter} instances for the specified entity. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - public static List getVariantsFromEntity(String pEntityName) { - return entityVariantsMap.getOrDefault(pEntityName, new ArrayList<>()); - } - - /** - * A {@link VariantParameter} method that retrieves a {@link VariantParameter} by its name for a specific entity. - * - * @param pEntityName {@link String} - The name of the entity to retrieve variants for. - * @param pVariantName {@link String} - The name of the variant to retrieve. - * @return The {@link VariantParameter} with the specified name, or {@code null} if not found. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - public static VariantParameter getVariantByName(String pEntityName, String pVariantName) { - List variants = getVariantsFromEntity(pEntityName); - for (VariantParameter variant : variants) { - if (variant.getVariantName().equals(pVariantName)) { - return variant; - } - } - return null; - } -} diff --git a/Forge/src/main/java/software/bluelib/entity/variant/VariantParameter.java b/Forge/src/main/java/software/bluelib/entity/variant/VariantParameter.java deleted file mode 100644 index db3d3319..00000000 --- a/Forge/src/main/java/software/bluelib/entity/variant/VariantParameter.java +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) BlueLib. Licensed under the MIT License. - -package software.bluelib.entity.variant; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import software.bluelib.entity.variant.base.ParameterBase; - -import java.util.Map; -import java.util.Set; - -/** - * A {@code VariantParameter} class that represents the parameters associated with a specific variant of an entity. - *

- * This class extends {@link ParameterBase} to store and manage variant-specific parameters parsed from a JSON object. - *

- * The class is designed to handle various JSON element types, including {@code JsonPrimitive}, {@code JsonArray}, and {@code JsonObject}. - *

- * Key Methods: - *
    - *
  • {@link #getJsonKey()} - Retrieves the key of the JSON object that identifies this entity.
  • - *
  • {@link #getVariantName()} - Retrieves the name of the variant.
  • - *
  • {@link #getParameter(String)} - Retrieves the value of a specific parameter by its key.
  • - *
- * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ -public class VariantParameter extends ParameterBase { - - /** - * A {@link String} that represents the key of the JSON object that identifies this entity. - *

- * This key is used to map the entity to its corresponding parameters within a {@link JsonObject}. - *

- * @Co-author MeAlam, Dan - * @since 1.0.0 - */ - private final String jsonKey; - - /** - * Constructs a new {@code VariantParameter} instance by extracting parameters from a given JSON object. - *

- * This constructor processes different types of {@link JsonElement} values: - *

    - *
  • {@code JsonPrimitive}: Stored directly as a string.
  • - *
  • {@code JsonArray}: Converts array elements into a single comma-separated string.
  • - *
  • {@code JsonObject}: Converts the nested JSON object to a string representation.
  • - *
  • {@code Other Types}: Stores "null" for unhandled JSON types.
  • - *
- *

- * @param pJsonKey {@link String} - The key that identifies this entity within the {@link JsonObject}. - * @param pJsonObject {@link JsonObject} - The {@link JsonObject} containing the variant parameters. - * @throws IllegalArgumentException if {@code pJsonKey} or {@code pJsonObject} is null. - * @see ParameterBase - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - public VariantParameter(String pJsonKey, JsonObject pJsonObject) { - if (pJsonKey == null || pJsonObject == null) { - throw new IllegalArgumentException("JSON key and object must not be null"); - } - this.jsonKey = pJsonKey; - Set> entryMap = pJsonObject.entrySet(); - for (Map.Entry entry : entryMap) { - JsonElement element = entry.getValue(); - if (element.isJsonPrimitive()) { - addParameter(entry.getKey(), element.getAsString()); - } else if (element.isJsonArray()) { - StringBuilder arrayValues = new StringBuilder(); - element.getAsJsonArray().forEach(e -> arrayValues.append(e.getAsString()).append(",")); - if (!arrayValues.isEmpty()) { - arrayValues.setLength(arrayValues.length() - 1); - } - addParameter(entry.getKey(), arrayValues.toString()); - } else if (element.isJsonObject()) { - addParameter(entry.getKey(), element.toString()); - } else { - addParameter(entry.getKey(), "null"); - } - } - } - - /** - * A {@link String} method that returns the key of the {@link JsonObject} that identifies this entity. - *

- * This key is typically used to retrieve or map the entity within a broader data structure. - *

- * @return The key of the JSON object representing this entity. - * @throws IllegalStateException if the key is unexpectedly null. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - public String getJsonKey() { - if (this.jsonKey == null) { - throw new IllegalStateException("JSON key should not be null"); - } - return this.jsonKey; - } - - /** - * A {@link String} method that retrieves the name of the variant. - *

- * The variant name is expected to be stored under the key {@code "variantName"} in the parameters/JSON Files. - *

- * @return The name of the variant, or {@code null} if the variant name is not found. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - public String getVariantName() { - return getParameter("variantName"); - } - - /** - * A {@link String} method that retrieves the value of a specific parameter by its key. - *

- * This method looks up the parameter's value within the internal data structure. - *

- * @param pKey {@link String} - The key of the parameter to retrieve. - * @return The value of the parameter, or {@code null} if the key does not exist. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - public String getParameter(String pKey) { - return (String) super.getParameter(pKey); - } -} diff --git a/Forge/src/main/java/software/bluelib/entity/variant/base/ParameterBase.java b/Forge/src/main/java/software/bluelib/entity/variant/base/ParameterBase.java deleted file mode 100644 index 2f1af3a1..00000000 --- a/Forge/src/main/java/software/bluelib/entity/variant/base/ParameterBase.java +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (c) BlueLib. Licensed under the MIT License. - -package software.bluelib.entity.variant.base; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -/** - * An {@code Abstract base class} for managing a collection of parameters. - *

- * Key Methods: - *

    - *
  • {@link #addParameter(String, Object)} - Adds a parameter to the collection.
  • - *
  • {@link #getParameter(String)} - Retrieves a parameter from the collection.
  • - *
  • {@link #removeParameter(String)} - Removes a parameter from the collection.
  • - *
  • {@link #getAllParameters()} - Returns all parameters in the collection.
  • - *
  • {@link #containsParameter(String)} - Checks if a parameter exists by its key.
  • - *
  • {@link #isEmpty()} - Checks if the collection of parameters is empty.
  • - *
  • {@link #clearParameters()} - Clears all parameters from the collection.
  • - *
  • {@link #getParameterCount()} - Returns the number of parameters in the collection.
  • - *
  • {@link #getParameterKeys()} - Returns a set of all parameter keys.
  • - *
  • {@link #getParameterValues()} - Returns a collection of all parameter values.
  • - *
  • {@link #updateParameter(String, Object)} - Updates the value of an existing parameter.
  • - *
- * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ -public abstract class ParameterBase { - - /** - * A {@link Map} to store parameters as key-value pairs. - * @Co-author MeAlam, Dan - * @since 1.0.0 - */ - private final Map parameters = new HashMap<>(); - - /** - * A {@code void} method to add a parameter to the collection. - * - * @param pKey {@link String} - The key under which the parameter is stored. - * @param pValue {@link Object} - The value of the parameter. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - protected void addParameter(String pKey, Object pValue) { - parameters.put(pKey, pValue); - } - - /** - * An {@link Object} method to retrieve a parameter from the collection by its key. - * - * @param pKey {@link String} - The key of the parameter to retrieve. - * @return The value associated with the key, or {@code null} if the key does not exist. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - protected Object getParameter(String pKey) { - return parameters.get(pKey); - } - - /** - * A {@code Void} that removes a parameter from the collection by its key. - * - * @param pKey {@link String} - The key of the parameter to remove. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - protected void removeParameter(String pKey) { - parameters.remove(pKey); - } - - /** - * A {@link Map} method that returns all parameters in the collection. - * - * @return A {@link Map} containing all parameters. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - protected Map getAllParameters() { - return new HashMap<>(parameters); - } - - /** - * A {@link Boolean} method that checks if a parameter exists by its key. - * - * @param pKey {@link String} - The key of the parameter to check. - * @return {@code true} if the parameter exists, {@code false} otherwise. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - protected boolean containsParameter(String pKey) { - return parameters.containsKey(pKey); - } - - /** - * A {@link Boolean} method that checks if the collection of parameters is empty. - * - * @return {@code true} if the collection is empty, {@code false} otherwise. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - protected boolean isEmpty() { - return parameters.isEmpty(); - } - - /** - * A {@code void} method that clears all parameters from the collection. - * - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - protected void clearParameters() { - parameters.clear(); - } - - /** - * An {@link Integer} method that returns the number of parameters in the collection. - * - * @return The number of parameters in the collection. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - protected int getParameterCount() { - return parameters.size(); - } - - /** - * A {@link Set} method that returns a set of all parameter keys. - * - * @return A {@link Set} containing all parameter keys. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - protected Set getParameterKeys() { - return parameters.keySet(); - } - - /** - * A {@link Collection} method that returns a collection of all parameter values. - * - * @return A {@link Collection} containing all parameter values. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - protected Collection getParameterValues() { - return parameters.values(); - } - - /** - * A {@code void} method that updates the value of an existing parameter. - * - * @param pKey {@link String} - The key of the parameter to update. - * @param pNewValue {@link Object} - The new value to set for the parameter. - * @throws IllegalArgumentException if the key does not exist. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - protected void updateParameter(String pKey, Object pNewValue) { - if (parameters.containsKey(pKey)) { - parameters.put(pKey, pNewValue); - } else { - throw new IllegalArgumentException("Key does not exist: " + pKey); - } - } -} diff --git a/Forge/src/main/java/software/bluelib/event/ReloadEventHandler.java b/Forge/src/main/java/software/bluelib/event/ReloadEventHandler.java deleted file mode 100644 index 9a095c52..00000000 --- a/Forge/src/main/java/software/bluelib/event/ReloadEventHandler.java +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) BlueLib. Licensed under the MIT License. - -package software.bluelib.event; - -import com.google.gson.JsonParseException; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.MinecraftServer; -import software.bluelib.entity.variant.VariantLoader; - -/** - * A {@code ReloadEventHandler} class responsible for handling events related to reloading entity variants. - *

- * This class includes methods for registering entity variants when the server starts. - *

- *

- * Key Features: - *

    - *
  • {@link #registerEntityVariants(MinecraftServer, String, String, String, String)} - Registers entity variants from specified locations.
  • - *
- *

- * @see VariantLoader - * @see MinecraftServer - * @see ResourceLocation - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ -public class ReloadEventHandler { - - /** - * A {@code void} method that registers entity variants from specified locations. - *

- * This method attempts to load variants from both mod and datapack locations, providing status information - * and handling any exceptions that occur during the loading process. - *

- *

- * Parameters: - *

    - *
  • {@code pServer} - The server instance of the current world.
  • - *
  • {@code pEntityName} - The entity name to load.
  • - *
  • {@code pModID} - The mod ID used to locate the entity variant resources. (Use your Mod's ID)
  • - *
  • {@code pModPathLocation} - The path location within the mod where variants are stored.
  • - *
  • {@code pDataPathLocation} - The path location within the resource pack where variants are stored.
  • - *
- *

- *

- * Exception Handling: - *

    - *
  • {@code JsonParseException} - Thrown when there is an error parsing the JSON files.
  • - *
  • {@code RuntimeException} - Thrown for unexpected errors during the registration process.
  • - *
- *

- * @param pServer {@link MinecraftServer} - The server instance of the current world. - * @param pEntityName {@link String} - The entity name to load. - * @param pModID {@link String} - The mod ID used to locate the entity variant resources. (Use your Mod's ID) - * @param pModPathLocation {@link String} - The path location within the mod where variants are stored. - * @param pDataPathLocation {@link String} - The path location within the datapack where variants are stored. - * @throws JsonParseException if there is an error parsing the JSON files. - * @throws RuntimeException if an unexpected error occurs during the registration process. - * @see MinecraftServer - * @see ResourceLocation - * @see VariantLoader - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - protected static void registerEntityVariants(MinecraftServer pServer, String pEntityName, String pModID, String pModPathLocation, String pDataPathLocation) { - ResourceLocation modLocation = new ResourceLocation(pModID, pModPathLocation); - ResourceLocation dataLocation = new ResourceLocation(pModID, pDataPathLocation); - try { - VariantLoader.loadVariants(modLocation, dataLocation, pServer, pEntityName); - } catch (JsonParseException pException) { - throw new RuntimeException("Failed to parse JSON(s) while registering entity variants for " + pEntityName + " from Mod with ModID: " + pModID, pException); - } catch (Exception pException) { - throw new RuntimeException("Unexpected error occurred while registering entity variants for " + pEntityName + " from Mod with ModID: " + pModID, pException); - } - } -} diff --git a/Forge/src/main/java/software/bluelib/example/entity/dragon/DragonModel.java b/Forge/src/main/java/software/bluelib/example/entity/dragon/DragonModel.java deleted file mode 100644 index 67091901..00000000 --- a/Forge/src/main/java/software/bluelib/example/entity/dragon/DragonModel.java +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) BlueLib. Licensed under the MIT License. - -package software.bluelib.example.entity.dragon; - -import net.minecraft.resources.ResourceLocation; -import software.bernie.geckolib.model.GeoModel; -import software.bluelib.BlueLib; - -public class DragonModel extends GeoModel { - - - // Get the Model Location - @Override - public ResourceLocation getModelResource(DragonEntity pObject) { - return new ResourceLocation(BlueLib.MODID, "geo/dragon.geo.json"); - } - - // Get the Texture Location - @Override - public ResourceLocation getTextureResource(DragonEntity pObject) { - return pObject.getTextureLocation(BlueLib.MODID, "textures/entity/" + pObject.entityName + "/" + pObject.getVariantName() + ".png"); - } - - // Get the Animation Location - @Override - public ResourceLocation getAnimationResource(DragonEntity pAnimatable) { - return new ResourceLocation(BlueLib.MODID, "animations/dragon.animation.json"); - } -} diff --git a/Forge/src/main/java/software/bluelib/example/entity/rex/RexModel.java b/Forge/src/main/java/software/bluelib/example/entity/rex/RexModel.java deleted file mode 100644 index 6084892c..00000000 --- a/Forge/src/main/java/software/bluelib/example/entity/rex/RexModel.java +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) BlueLib. Licensed under the MIT License. - -package software.bluelib.example.entity.rex; - -import net.minecraft.resources.ResourceLocation; -import software.bernie.geckolib.model.GeoModel; -import software.bluelib.BlueLib; - -public class RexModel extends GeoModel { - - - // Get the Model Location - @Override - public ResourceLocation getModelResource(RexEntity pObject) { - return new ResourceLocation(BlueLib.MODID, "geo/rex.geo.json"); - } - - // Get the Texture Location - @Override - public ResourceLocation getTextureResource(RexEntity pObject) { - return pObject.getTextureLocation(BlueLib.MODID, "textures/entity/" + pObject.entityName + "/" + pObject.getVariantName() + ".png"); - } - - // Get the Animation Location - @Override - public ResourceLocation getAnimationResource(RexEntity pAnimatable) { - return new ResourceLocation(BlueLib.MODID, "animations/rex.animation.json"); - } -} diff --git a/Forge/src/main/java/software/bluelib/example/event/ClientEvents.java b/Forge/src/main/java/software/bluelib/example/event/ClientEvents.java deleted file mode 100644 index 187fc9ab..00000000 --- a/Forge/src/main/java/software/bluelib/example/event/ClientEvents.java +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) BlueLib. Licensed under the MIT License. - -package software.bluelib.example.event; - -import net.minecraft.client.renderer.entity.EntityRenderers; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.fml.common.Mod; -import software.bluelib.BlueLib; -import software.bluelib.example.entity.dragon.DragonRender; -import software.bluelib.example.entity.rex.RexRender; -import software.bluelib.example.init.ModEntities; - -@Mod.EventBusSubscriber(modid = BlueLib.MODID, bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT) -public class ClientEvents { - - public static void registerRenderers() { - // Register the renderer for all the Entities - EntityRenderers.register(ModEntities.DRAGON.get(), DragonRender::new); - EntityRenderers.register(ModEntities.REX.get(), RexRender::new); - } -} diff --git a/Forge/src/main/java/software/bluelib/example/event/CommonModEvent.java b/Forge/src/main/java/software/bluelib/example/event/CommonModEvent.java deleted file mode 100644 index c45cfb03..00000000 --- a/Forge/src/main/java/software/bluelib/example/event/CommonModEvent.java +++ /dev/null @@ -1,18 +0,0 @@ -package software.bluelib.example.event; - -import net.minecraftforge.event.entity.EntityAttributeCreationEvent; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.common.Mod; -import software.bluelib.BlueLib; -import software.bluelib.example.entity.dragon.DragonEntity; -import software.bluelib.example.entity.rex.RexEntity; -import software.bluelib.example.init.ModEntities; - -@Mod.EventBusSubscriber(modid = BlueLib.MODID, bus = Mod.EventBusSubscriber.Bus.MOD) -public class CommonModEvent { - @SubscribeEvent - public static void onAttributeCreate(EntityAttributeCreationEvent pEvent) { - pEvent.put(ModEntities.DRAGON.get(), DragonEntity.createAttributes().build()); - pEvent.put(ModEntities.REX.get(), RexEntity.createAttributes().build()); - } - } diff --git a/Forge/src/main/java/software/bluelib/example/proxy/ClientProxy.java b/Forge/src/main/java/software/bluelib/example/proxy/ClientProxy.java deleted file mode 100644 index 4b0778aa..00000000 --- a/Forge/src/main/java/software/bluelib/example/proxy/ClientProxy.java +++ /dev/null @@ -1,19 +0,0 @@ -package software.bluelib.example.proxy; - -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.fml.common.Mod; -import software.bluelib.BlueLib; -import software.bluelib.example.event.ClientEvents; - -@Mod.EventBusSubscriber(modid = BlueLib.MODID, value = Dist.CLIENT) -public class ClientProxy extends CommonProxy { - - @Override - public void postInit() {} - - @Override - public void clientInit() { - super.clientInit(); - ClientEvents.registerRenderers(); - } -} diff --git a/Forge/src/main/java/software/bluelib/example/proxy/CommonProxy.java b/Forge/src/main/java/software/bluelib/example/proxy/CommonProxy.java deleted file mode 100644 index ad07e974..00000000 --- a/Forge/src/main/java/software/bluelib/example/proxy/CommonProxy.java +++ /dev/null @@ -1,13 +0,0 @@ -package software.bluelib.example.proxy; - -import net.minecraftforge.fml.common.Mod; -import software.bluelib.BlueLib; - -@Mod.EventBusSubscriber(modid = BlueLib.MODID, bus = Mod.EventBusSubscriber.Bus.MOD) -public class CommonProxy { - - public void postInit() {} - - public void clientInit() { - } -} diff --git a/Forge/src/main/java/software/bluelib/exception/CouldNotLoadJSON.java b/Forge/src/main/java/software/bluelib/exception/CouldNotLoadJSON.java deleted file mode 100644 index c93d1015..00000000 --- a/Forge/src/main/java/software/bluelib/exception/CouldNotLoadJSON.java +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) BlueLib. Licensed under the MIT License. - -package software.bluelib.exception; - -/** - * A {@code RuntimeException} that represents an error when a JSON file could not be loaded. - *

- * This exception provides additional context by including the {@link #getResourceId()} of the JSON file that failed to load. - *

- *

- * Key Features: - *

    - *
  • {@link #getResourceId()} - Retrieves the ID of the resource that could not be loaded.
  • - *
- *

- * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ -public class CouldNotLoadJSON extends RuntimeException { - - /** - * A {@link String} that represents the ID of the resource that could not be loaded. - *

- * This ID is used to provide additional context for the error. - *

- * @Co-author MeAlam, Dan - * @since 1.0.0 - */ - private final String resourceId; - - /** - * Constructs a new {@code CouldNotLoadJSON} exception with the specified detail message and resource ID. - *

- * The detail message provides information about the nature of the error, while the resource ID indicates - * which specific resource could not be loaded. - *

- * - * @param pMessage {@link String} - The detail message explaining the reason for the exception. - * @param pResourceId {@link String} - The ID of the resource that could not be loaded. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - public CouldNotLoadJSON(String pMessage, String pResourceId) { - super(pMessage); - this.resourceId = pResourceId; - } - - /** - * A {@link String} that retrieves the resource ID of the JSON file that could not be loaded. - *

- * This method provides access to the ID of the resource that caused the exception. - *

- * - * @return The resource ID as a {@link String}. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - public String getResourceId() { - return resourceId; - } -} diff --git a/Forge/src/main/java/software/bluelib/interfaces/variant/IVariantEntity.java b/Forge/src/main/java/software/bluelib/interfaces/variant/IVariantEntity.java deleted file mode 100644 index 5273ec4e..00000000 --- a/Forge/src/main/java/software/bluelib/interfaces/variant/IVariantEntity.java +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) BlueLib. Licensed under the MIT License. - -package software.bluelib.interfaces.variant; - -import software.bluelib.interfaces.variant.base.IVariantEntityBase; - -import java.util.List; -import java.util.Random; - -/** - * An {@code Interface} representing an entity that supports multiple variants. - *

- * This interface extends {@link IVariantEntityBase} to include methods specific to handling entity variants, including - * random selection of variants. - *

- *

- * Key Methods: - *

    - *
  • {@link #getRandomVariant(List, String)} - Retrieves a random variant name from a provided list or defaults if the list is empty.
  • - *
- *

- * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ -public interface IVariantEntity extends IVariantEntityBase { - - /** - * A {@link Random} instance used for generating random variants. - * @Co-author MeAlam, Dan - * @since 1.0.0 - */ - Random random = new Random(); - - /** - * A {@link String} that selects a random variant name from the provided list of variant names. - *

- * This method uses the {@link Random} to pick a random variant from the list. If the list is empty, the default - * variant name is returned. - *

- * - * @param pVariantNamesList {@link List} - A {@link List} of variant names available for the entity. - * @param pDefaultVariant {@link String} - The default variant name to return if {@code pVariantNamesList} is empty. - * @return A random variant name from the list, or the default variant if the list is empty. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - default String getRandomVariant(List pVariantNamesList, String pDefaultVariant) { - List spawnableVariants = List.copyOf(pVariantNamesList); - if (!spawnableVariants.isEmpty()) { - return spawnableVariants.get(random.nextInt(spawnableVariants.size())); - } - return pDefaultVariant; - } -} diff --git a/Forge/src/main/java/software/bluelib/utils/ParameterUtils.java b/Forge/src/main/java/software/bluelib/utils/ParameterUtils.java deleted file mode 100644 index 3994cdf5..00000000 --- a/Forge/src/main/java/software/bluelib/utils/ParameterUtils.java +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright (c) BlueLib. Licensed under the MIT License. - -package software.bluelib.utils; - -import software.bluelib.entity.variant.VariantParameter; -import software.bluelib.entity.variant.VariantLoader; - -import java.util.HashMap; -import java.util.Map; -import java.util.NoSuchElementException; - -/** - * A {@code Class} for managing custom parameters associated with entity variants. - *

- * This class provides methods to retrieve custom parameters for variants and allows - * for building and connecting parameters to specific variants using the {@link ParameterBuilder}. - *

- *

- * Key Methods: - *

    - *
  • {@link #getParameter(String, String)} - Retrieves the value of a custom parameter for a specific variant.
  • - *
- *

- * Nested Classes: - *
    - *
  • {@link ParameterBuilder} - A builder class for creating and associating custom parameters with a specific variant.
  • - *
- *

- * @since 1.0.0 - * @author MeAlam - * @Co-author Dan - */ -public class ParameterUtils { - - /** - * A {@link Map} holding custom parameters for each variant. - *

- * The outer map's key is the variant's name, and the inner map contains key-value pairs - * of custom parameters for that variant. - *

- * @Co-author MeAlam, Dan - * @since 1.0.0 - */ - private static final Map> variantParametersMap = new HashMap<>(); - - /** - * Retrieves the value of a custom parameter for a specific variant. - *

- * If the parameter is not found, "null" is returned. - *

- * - * @param pVariantName {@link String} - The name of the variant. - * @param pParameterKey {@link String} - The key of the parameter to retrieve. - * @return The value of the custom parameter for the specified variant. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - public static String getParameter(String pVariantName, String pParameterKey) { - return variantParametersMap.getOrDefault(pVariantName, new HashMap<>()).getOrDefault(pParameterKey, "null"); - } - - /** - * A {@code Builder} class for creating and associating custom parameters with a specific variant. - *

- * This class allows chaining methods to build and connect parameters to a variant. - *

- * - *

- * Key Methods: - *

    - *
  • {@link #forVariant(String, String)} - Creates a new instance of {@link ParameterBuilder} for the specified entity and variant.
  • - *
  • {@link #withParameter(String)} - Adds a parameter to the parameters map with a default value of "null".
  • - *
  • {@link #connect()} - Adds parameters to the variant and updates the static {@link VariantParameter} with these parameters.
  • - *
- *

- *

- * **Note:** The "null" value is used only if the parameter is not specified in the JSON files. - *

- * @since 1.0.0 - * @author MeAlam - * @Co-author Dan - */ - public static class ParameterBuilder { - /** - * The name of the variant for which parameters are being built. - * @Co-author MeAlam, Dan - * @since 1.0.0 - */ - private final String variantName; - - /** - * The name of the entity for which parameters are being built. - * @Co-author MeAlam, Dan - * @since 1.0.0 - */ - private final String entityName; - - /** - * A {@link Map} to store parameters for the variant. - *

- * Each key-value pair represents a parameter name and its default value. - *

- * @Co-author MeAlam, Dan - * @since 1.0.0 - */ - private final Map parameters = new HashMap<>(); - - /** - * Constructor to initialize the builder with a specific entity name and variant name. - * - * @param pEntityName {@link String} - The name of the entity. - * @param pVariantName {@link String} - The name of the variant. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - private ParameterBuilder(String pEntityName, String pVariantName) { - this.variantName = pVariantName; - this.entityName = pEntityName; - } - - /** - * Creates a new instance of {@link ParameterBuilder} for the specified entity and variant. - * - * @param pEntityName {@link String} - The name of the entity. - * @param pVariantName {@link String} - The name of the variant. - * @return A new instance of {@link ParameterBuilder}. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - public static ParameterBuilder forVariant(String pEntityName, String pVariantName) { - return new ParameterBuilder(pEntityName, pVariantName); - } - - /** - * Adds a parameter to the parameters map with a default value of "null".
- *

- * **Note:** The "null" value is used only if the parameter is not specified in the JSON files. - *

- * - * @param pParameter {@link String} - The key of the parameter to add. - * @return The current instance of {@link ParameterBuilder} for method chaining. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - public ParameterBuilder withParameter(String pParameter) { - parameters.put(pParameter, "null"); - return this; - } - - /** - * Adds a parameter to the parameters map with a specified default value. - *

- * Connects the parameters built with this builder to the specified variant. - * Updates the static {@link VariantParameter} with the parameters for the specified variant. - *

- * - * @return The current instance of {@link ParameterBuilder} for method chaining. - * @throws NoSuchElementException if the specified variant is not found for the entity. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - public ParameterBuilder connect() { - VariantParameter variant = VariantLoader.getVariantByName(entityName, variantName); - if (variant != null) { - Map updatedParameters = new HashMap<>(); - for (String key : parameters.keySet()) { - updatedParameters.put(key, variant.getParameter(key)); - } - variantParametersMap.put(variantName, updatedParameters); - } else { - throw new NoSuchElementException("Variant '" + variantName + "' not found for entity '" + entityName + "'"); - } - return this; - } - } -} diff --git a/Forge/src/main/resources/pack.mcmeta b/Forge/src/main/resources/pack.mcmeta deleted file mode 100644 index 001e27cb..00000000 --- a/Forge/src/main/resources/pack.mcmeta +++ /dev/null @@ -1,6 +0,0 @@ -{ - "pack": { - "description": "bluelib", - "pack_format": 22 - } -} \ No newline at end of file diff --git a/NeoForge/.gitignore b/NeoForge/.gitignore deleted file mode 100644 index 31d25505..00000000 --- a/NeoForge/.gitignore +++ /dev/null @@ -1,26 +0,0 @@ -# eclipse -bin -*.launch -.settings -.metadata -.classpath -.project - -# idea -out -*.ipr -*.iws -*.iml -.idea - -# gradle -build -.gradle - -# other -eclipse -run -runs -run-data - -repo \ No newline at end of file diff --git a/NeoForge/build.gradle b/NeoForge/build.gradle deleted file mode 100644 index 158ee7ed..00000000 --- a/NeoForge/build.gradle +++ /dev/null @@ -1,114 +0,0 @@ - plugins { - id 'java-library' - id 'eclipse' - id 'idea' - id 'maven-publish' - id 'net.neoforged.gradle.userdev' version '7.0.97' -} - -version = mod_version -group = mod_group_id - -repositories { - maven { - url "https://cursemaven.com" - } - mavenCentral() - mavenLocal() -} - -base { - archivesName = "${mod_id}-neoforge-${minecraft_version}" -} - -java.toolchain.languageVersion = JavaLanguageVersion.of(java_version) - -//minecraft.accessTransformers.file rootProject.file('src/main/resources/META-INF/accesstransformer.cfg') -//minecraft.accessTransformers.entry public net.minecraft.client.Minecraft textureManager # textureManager - -runs { - configureEach { - systemProperty 'forge.logging.markers', 'REGISTRIES' - - // Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels - systemProperty 'forge.logging.console.level', 'debug' - - modSource project.sourceSets.main - } - - client { - systemProperty 'forge.enabledGameTestNamespaces', project.mod_id - } - - server { - systemProperty 'forge.enabledGameTestNamespaces', project.mod_id - programArgument '--nogui' - } - - gameTestServer { - systemProperty 'forge.enabledGameTestNamespaces', project.mod_id - } - - data { - programArguments.addAll '--mod', project.mod_id, '--all', '--output', file('src/generated/resources/').getAbsolutePath(), '--existing', file('src/main/resources/').getAbsolutePath() - } -} - -sourceSets.main.resources { srcDir 'src/generated/resources' } - -configurations { - runtimeClasspath.extendsFrom localRuntime -} - -dependencies { - implementation "net.neoforged:neoforge:${neo_version}" - runtimeOnly "curse.maven:geckolib-388172:${geckolib_file}" - compileOnly "curse.maven:geckolib-388172:${geckolib_file}" -} - -jar { - manifest { - attributes([ - "Specification-Title" : mod_name, - "Specification-Vendor" : mod_authors, - "Specification-Version" : mod_version, - "Implementation-Title" : mod_name, - "Implementation-Version" : mod_version, - "Implementation-Vendor" : mod_authors, - "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"), - ]) - } -} - -// See https://docs.gradle.org/current/dsl/org.gradle.language.jvm.tasks.ProcessResources.html -tasks.withType(ProcessResources).configureEach { - var replaceProperties = [ - minecraft_version : minecraft_version, - minecraft_version_range: minecraft_version_range, - neo_version : neo_version, - neo_version_range : neo_version_range, - loader_version_range : loader_version_range, - mod_id : mod_id, - mod_name : mod_name, - mod_license : mod_license, - mod_version : mod_version, - mod_authors : mod_authors, - mod_description : mod_description - ] - inputs.properties replaceProperties - - filesMatching(['META-INF/mods.toml']) { - expand replaceProperties - } -} - -tasks.withType(JavaCompile).configureEach { - options.encoding = 'UTF-8' -} - -idea { - module { - downloadSources = true - downloadJavadoc = true - } -} diff --git a/NeoForge/changelog.txt b/NeoForge/changelog.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/NeoForge/gradle.properties b/NeoForge/gradle.properties deleted file mode 100644 index 979cf055..00000000 --- a/NeoForge/gradle.properties +++ /dev/null @@ -1,25 +0,0 @@ -#org.gradle.jvmargs= -org.gradle.daemon=false -org.gradle.debug=false - -neogradle.subsystems.parchment.minecraftVersion=1.20.3 -neogradle.subsystems.parchment.mappingsVersion=2023.12.31 -minecraft_version=1.20.4 -minecraft_version_range=[1.20.2,1.21) -neo_version=20.4.237 -neo_version_range=[20.4,) -loader_version_range=[2,) -## geckolib_file is the cursemaven file you get from curseforge -geckolib_file=5188406 -java_version=17 - - -## Mod Properties - -mod_id=bluelib -mod_name=BlueLib -mod_license=MIT License -mod_version=1.0.0 -mod_group_id=software.bluelib -mod_authors=Dan, Aram -mod_description=BlueLib is an All round Minecraft mod library that offers data-driven features, allowing users to implement and customize its features with full freedom. \nIt supports both Resource and Datapacks, ensuring seamless integration and flexibility. \ No newline at end of file diff --git a/NeoForge/settings.gradle b/NeoForge/settings.gradle deleted file mode 100644 index ada876e2..00000000 --- a/NeoForge/settings.gradle +++ /dev/null @@ -1,11 +0,0 @@ -pluginManagement { - repositories { - mavenLocal() - gradlePluginPortal() - maven { url = 'https://maven.neoforged.net/releases' } - } -} - -plugins { - id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0' -} diff --git a/NeoForge/src/main/java/software/bluelib/BlueLib.java b/NeoForge/src/main/java/software/bluelib/BlueLib.java deleted file mode 100644 index e73d1c98..00000000 --- a/NeoForge/src/main/java/software/bluelib/BlueLib.java +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) BlueLib. Licensed under the MIT License. - -package software.bluelib; - -import net.neoforged.bus.api.IEventBus; -import net.neoforged.bus.api.SubscribeEvent; -import net.neoforged.fml.common.Mod; -import net.neoforged.fml.event.lifecycle.FMLLoadCompleteEvent; -import net.neoforged.fml.loading.FMLEnvironment; -import software.bluelib.example.event.ClientEvents; -import software.bluelib.example.init.ModEntities; - -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -/** - * The main class of the {@link BlueLib} mod. - *

- * This class serves as the entry point for the {@link BlueLib} mod, handling initialization by registering event handlers - * and setting up necessary configurations. For more details, refer to the BlueLib Wiki. - *

- * - *

- * Key Methods: - *

    - *
  • {@link #BlueLib(IEventBus)} - Constructs the {@link BlueLib} instance and registers the mod event bus.
  • - *
  • {@link #onLoadComplete(FMLLoadCompleteEvent)} - Handles the event when the mod loading is complete and prints a thank-you message if in developer mode.
  • - *
  • {@link #isDeveloperMode()} - Determines if the mod is running in developer mode.
  • - *
- *

- * - * @see BlueLib Wiki - * @author MeAlam, Dan - * @Co-author All Contributors of BlueLib! - * @since 1.0.0 - */ -@Mod(BlueLib.MODID) -public class BlueLib { - - /** - * A {@link ScheduledExecutorService} used for scheduling tasks, such as printing messages after a delay. - *

- * This is initialized with a single-threaded pool to handle delayed tasks in a separate thread. - *

- * @Co-author MeAlam, Dan - * @since 1.0.0 - */ - private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); - - /** - * The Mod ID for {@link BlueLib}. This serves as a unique identifier for the mod. - * @Co-author MeAlam, Dan - * @since 1.0.0 - */ - public static final String MODID = "bluelib"; - - // public static final Logger LOGGER = LogUtils.getLogger(); - - /** - * Constructs a new {@link BlueLib} instance and registers the mod event bus. - * - * @param pModEventBus {@link IEventBus} - The event bus to which the mod will register its event handlers. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - public BlueLib(IEventBus pModEventBus) { - pModEventBus.register(this); - if (isDeveloperMode()) { - ModEntities.REGISTRY.register(pModEventBus); - - pModEventBus.addListener(ClientEvents::registerAttributes); - pModEventBus.addListener(ClientEvents::registerRenderers); - } - } - - /** - * Handles the {@link FMLLoadCompleteEvent}, which is triggered when the mod loading process is complete. - *

- * If the mod is running in developer mode, it schedules a task to print a thank-you message to the console after a short delay. - *

- * - * @param pEvent {@link FMLLoadCompleteEvent} - The event triggered upon the completion of the mod loading process. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - @SubscribeEvent - public void onLoadComplete(FMLLoadCompleteEvent pEvent) { - if (isDeveloperMode()) { - scheduler.schedule(() -> { - System.out.println(""" - - ************************************************** - * * - * Thank you for using BlueLib! * - * We appreciate your support. * - * * - ************************************************** - """); - scheduler.shutdown(); - }, 3, TimeUnit.SECONDS); - } - } - - /** - * Checks if the mod is running in developer mode. - *

- * Developer mode is determined by checking if the mod is not running in a production environment. - *

- * - * @return {@code true} if the mod is running in developer mode, {@code false} otherwise. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - static boolean isDeveloperMode() { - return !FMLEnvironment.production; - } -} diff --git a/NeoForge/src/main/java/software/bluelib/entity/variant/VariantLoader.java b/NeoForge/src/main/java/software/bluelib/entity/variant/VariantLoader.java deleted file mode 100644 index 3dc0034b..00000000 --- a/NeoForge/src/main/java/software/bluelib/entity/variant/VariantLoader.java +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright (c) BlueLib. Licensed under the MIT License. - -package software.bluelib.entity.variant; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.packs.resources.ResourceManager; -import software.bluelib.interfaces.variant.base.IVariantEntityBase; -import software.bluelib.json.JSONLoader; -import software.bluelib.json.JSONMerger; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * A {@link VariantLoader} class that loads and manages {@link VariantParameter} for entities by merging JSON data from multiple sources. - *

- * Key Methods: - *

    - *
  • {@link #loadVariants(ResourceLocation, ResourceLocation, MinecraftServer, String)} - Loads and merges variant data from both the main mod and the latest datapack.
  • - *
  • {@link #getVariantsFromEntity(String)} - Retrieves the list of loaded {@link VariantParameter} for a specific entity.
  • - *
  • {@link #getVariantByName(String, String)} - Retrieves a specific {@link VariantParameter} by its name for a given entity.
  • - *
- * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ -public class VariantLoader implements IVariantEntityBase { - - /** - * A {@link Map} to store loaded {@link VariantParameter} for each entity type. - * @Co-author MeAlam, Dan - * @since 1.0.0 - */ - private static final Map> entityVariantsMap = new HashMap<>(); - - /** - * A {@link JSONLoader} instance to load JSON data. - * @Co-author MeAlam, Dan - * @since 1.0.0 - */ - private static final JSONLoader jsonLoader = new JSONLoader(); - - /** - * A {@link JSONMerger} instance to merge JSON data. - * @Co-author MeAlam, Dan - * @since 1.0.0 - */ - private static final JSONMerger jsonMerger = new JSONMerger(); - - /** - * A {@code void} method that loads and merges variant data from both the Main Mod and the Latest Datapack. - * Parses the merged data into {@link VariantParameter}. - * - * @param pJSONLocationMod {@link ResourceLocation} - The {@link ResourceLocation} of the Mod's JSON data. - * @param pJSONLocationData {@link ResourceLocation} - The {@link ResourceLocation} of the Latest DataPack's JSON data. - * @param pServer {@link MinecraftServer} - The {@link MinecraftServer} instance. - * @param pEntityName {@link String} - The name of the entity whose variants should be cleared before loading new ones. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - public static void loadVariants(ResourceLocation pJSONLocationMod, ResourceLocation pJSONLocationData, MinecraftServer pServer, String pEntityName) { - clearVariantsForEntity(pEntityName); - ResourceManager resourceManager = pServer.getResourceManager(); - JsonObject mergedJsonObject = new JsonObject(); - - JsonObject modJson = jsonLoader.loadJson(pJSONLocationMod, resourceManager); - JsonObject dataJson = jsonLoader.loadJson(pJSONLocationData, resourceManager); - - jsonMerger.mergeJsonObjects(mergedJsonObject, modJson); - jsonMerger.mergeJsonObjects(mergedJsonObject, dataJson); - - parseVariants(mergedJsonObject); - } - - /** - * A {@code void} method that clears variants for a specific entity type from the map. - * - * @param pEntityName {@link String} - The name of the entity whose variants should be cleared. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - private static void clearVariantsForEntity(String pEntityName) { - entityVariantsMap.remove(pEntityName); - } - - /** - * A {@code void} method that parses the merged JSON data and converts it into {@link VariantParameter} instances. - * - * @param pJsonObject {@link JsonObject} - The merged {@link JsonObject} containing variant data. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - private static void parseVariants(JsonObject pJsonObject) { - for (Map.Entry entry : pJsonObject.entrySet()) { - String entityName = entry.getKey(); - JsonArray textureArray = entry.getValue().getAsJsonArray(); - - List variantList = entityVariantsMap.computeIfAbsent(entityName, k -> new ArrayList<>()); - - for (JsonElement variant : textureArray) { - VariantParameter newVariant = getEntityVariant(entityName, variant.getAsJsonObject()); - - boolean variantExists = variantList.stream() - .anyMatch(v -> v.equals(newVariant)); - - if (!variantExists) { - variantList.add(newVariant); - } - } - } - } - - /** - * A {@link VariantParameter} method that creates a new {@link VariantParameter} instance from a JSON object. - * - * @param pJsonKey {@link String} - The key associated with this variant. - * @param pJsonObject {@link JsonObject} - The {@link JsonObject} containing the variant data. - * @return A {@link VariantParameter} instance. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - private static VariantParameter getEntityVariant(String pJsonKey, JsonObject pJsonObject) { - return new VariantParameter(pJsonKey, pJsonObject); - } - - /** - * A {@link List} method that retrieves the {@link List} of loaded {@link VariantParameter} - * for a specific entity. - * - * @param pEntityName {@link String} - The name of the entity to retrieve variants for. - * @return A {@link List} of {@link VariantParameter} instances for the specified entity. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - public static List getVariantsFromEntity(String pEntityName) { - return entityVariantsMap.getOrDefault(pEntityName, new ArrayList<>()); - } - - /** - * A {@link VariantParameter} method that retrieves a {@link VariantParameter} by its name for a specific entity. - * - * @param pEntityName {@link String} - The name of the entity to retrieve variants for. - * @param pVariantName {@link String} - The name of the variant to retrieve. - * @return The {@link VariantParameter} with the specified name, or {@code null} if not found. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - public static VariantParameter getVariantByName(String pEntityName, String pVariantName) { - List variants = getVariantsFromEntity(pEntityName); - for (VariantParameter variant : variants) { - if (variant.getVariantName().equals(pVariantName)) { - return variant; - } - } - return null; - } -} diff --git a/NeoForge/src/main/java/software/bluelib/entity/variant/VariantParameter.java b/NeoForge/src/main/java/software/bluelib/entity/variant/VariantParameter.java deleted file mode 100644 index db3d3319..00000000 --- a/NeoForge/src/main/java/software/bluelib/entity/variant/VariantParameter.java +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) BlueLib. Licensed under the MIT License. - -package software.bluelib.entity.variant; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import software.bluelib.entity.variant.base.ParameterBase; - -import java.util.Map; -import java.util.Set; - -/** - * A {@code VariantParameter} class that represents the parameters associated with a specific variant of an entity. - *

- * This class extends {@link ParameterBase} to store and manage variant-specific parameters parsed from a JSON object. - *

- * The class is designed to handle various JSON element types, including {@code JsonPrimitive}, {@code JsonArray}, and {@code JsonObject}. - *

- * Key Methods: - *
    - *
  • {@link #getJsonKey()} - Retrieves the key of the JSON object that identifies this entity.
  • - *
  • {@link #getVariantName()} - Retrieves the name of the variant.
  • - *
  • {@link #getParameter(String)} - Retrieves the value of a specific parameter by its key.
  • - *
- * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ -public class VariantParameter extends ParameterBase { - - /** - * A {@link String} that represents the key of the JSON object that identifies this entity. - *

- * This key is used to map the entity to its corresponding parameters within a {@link JsonObject}. - *

- * @Co-author MeAlam, Dan - * @since 1.0.0 - */ - private final String jsonKey; - - /** - * Constructs a new {@code VariantParameter} instance by extracting parameters from a given JSON object. - *

- * This constructor processes different types of {@link JsonElement} values: - *

    - *
  • {@code JsonPrimitive}: Stored directly as a string.
  • - *
  • {@code JsonArray}: Converts array elements into a single comma-separated string.
  • - *
  • {@code JsonObject}: Converts the nested JSON object to a string representation.
  • - *
  • {@code Other Types}: Stores "null" for unhandled JSON types.
  • - *
- *

- * @param pJsonKey {@link String} - The key that identifies this entity within the {@link JsonObject}. - * @param pJsonObject {@link JsonObject} - The {@link JsonObject} containing the variant parameters. - * @throws IllegalArgumentException if {@code pJsonKey} or {@code pJsonObject} is null. - * @see ParameterBase - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - public VariantParameter(String pJsonKey, JsonObject pJsonObject) { - if (pJsonKey == null || pJsonObject == null) { - throw new IllegalArgumentException("JSON key and object must not be null"); - } - this.jsonKey = pJsonKey; - Set> entryMap = pJsonObject.entrySet(); - for (Map.Entry entry : entryMap) { - JsonElement element = entry.getValue(); - if (element.isJsonPrimitive()) { - addParameter(entry.getKey(), element.getAsString()); - } else if (element.isJsonArray()) { - StringBuilder arrayValues = new StringBuilder(); - element.getAsJsonArray().forEach(e -> arrayValues.append(e.getAsString()).append(",")); - if (!arrayValues.isEmpty()) { - arrayValues.setLength(arrayValues.length() - 1); - } - addParameter(entry.getKey(), arrayValues.toString()); - } else if (element.isJsonObject()) { - addParameter(entry.getKey(), element.toString()); - } else { - addParameter(entry.getKey(), "null"); - } - } - } - - /** - * A {@link String} method that returns the key of the {@link JsonObject} that identifies this entity. - *

- * This key is typically used to retrieve or map the entity within a broader data structure. - *

- * @return The key of the JSON object representing this entity. - * @throws IllegalStateException if the key is unexpectedly null. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - public String getJsonKey() { - if (this.jsonKey == null) { - throw new IllegalStateException("JSON key should not be null"); - } - return this.jsonKey; - } - - /** - * A {@link String} method that retrieves the name of the variant. - *

- * The variant name is expected to be stored under the key {@code "variantName"} in the parameters/JSON Files. - *

- * @return The name of the variant, or {@code null} if the variant name is not found. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - public String getVariantName() { - return getParameter("variantName"); - } - - /** - * A {@link String} method that retrieves the value of a specific parameter by its key. - *

- * This method looks up the parameter's value within the internal data structure. - *

- * @param pKey {@link String} - The key of the parameter to retrieve. - * @return The value of the parameter, or {@code null} if the key does not exist. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - public String getParameter(String pKey) { - return (String) super.getParameter(pKey); - } -} diff --git a/NeoForge/src/main/java/software/bluelib/entity/variant/base/ParameterBase.java b/NeoForge/src/main/java/software/bluelib/entity/variant/base/ParameterBase.java deleted file mode 100644 index 2f1af3a1..00000000 --- a/NeoForge/src/main/java/software/bluelib/entity/variant/base/ParameterBase.java +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (c) BlueLib. Licensed under the MIT License. - -package software.bluelib.entity.variant.base; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -/** - * An {@code Abstract base class} for managing a collection of parameters. - *

- * Key Methods: - *

    - *
  • {@link #addParameter(String, Object)} - Adds a parameter to the collection.
  • - *
  • {@link #getParameter(String)} - Retrieves a parameter from the collection.
  • - *
  • {@link #removeParameter(String)} - Removes a parameter from the collection.
  • - *
  • {@link #getAllParameters()} - Returns all parameters in the collection.
  • - *
  • {@link #containsParameter(String)} - Checks if a parameter exists by its key.
  • - *
  • {@link #isEmpty()} - Checks if the collection of parameters is empty.
  • - *
  • {@link #clearParameters()} - Clears all parameters from the collection.
  • - *
  • {@link #getParameterCount()} - Returns the number of parameters in the collection.
  • - *
  • {@link #getParameterKeys()} - Returns a set of all parameter keys.
  • - *
  • {@link #getParameterValues()} - Returns a collection of all parameter values.
  • - *
  • {@link #updateParameter(String, Object)} - Updates the value of an existing parameter.
  • - *
- * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ -public abstract class ParameterBase { - - /** - * A {@link Map} to store parameters as key-value pairs. - * @Co-author MeAlam, Dan - * @since 1.0.0 - */ - private final Map parameters = new HashMap<>(); - - /** - * A {@code void} method to add a parameter to the collection. - * - * @param pKey {@link String} - The key under which the parameter is stored. - * @param pValue {@link Object} - The value of the parameter. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - protected void addParameter(String pKey, Object pValue) { - parameters.put(pKey, pValue); - } - - /** - * An {@link Object} method to retrieve a parameter from the collection by its key. - * - * @param pKey {@link String} - The key of the parameter to retrieve. - * @return The value associated with the key, or {@code null} if the key does not exist. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - protected Object getParameter(String pKey) { - return parameters.get(pKey); - } - - /** - * A {@code Void} that removes a parameter from the collection by its key. - * - * @param pKey {@link String} - The key of the parameter to remove. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - protected void removeParameter(String pKey) { - parameters.remove(pKey); - } - - /** - * A {@link Map} method that returns all parameters in the collection. - * - * @return A {@link Map} containing all parameters. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - protected Map getAllParameters() { - return new HashMap<>(parameters); - } - - /** - * A {@link Boolean} method that checks if a parameter exists by its key. - * - * @param pKey {@link String} - The key of the parameter to check. - * @return {@code true} if the parameter exists, {@code false} otherwise. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - protected boolean containsParameter(String pKey) { - return parameters.containsKey(pKey); - } - - /** - * A {@link Boolean} method that checks if the collection of parameters is empty. - * - * @return {@code true} if the collection is empty, {@code false} otherwise. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - protected boolean isEmpty() { - return parameters.isEmpty(); - } - - /** - * A {@code void} method that clears all parameters from the collection. - * - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - protected void clearParameters() { - parameters.clear(); - } - - /** - * An {@link Integer} method that returns the number of parameters in the collection. - * - * @return The number of parameters in the collection. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - protected int getParameterCount() { - return parameters.size(); - } - - /** - * A {@link Set} method that returns a set of all parameter keys. - * - * @return A {@link Set} containing all parameter keys. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - protected Set getParameterKeys() { - return parameters.keySet(); - } - - /** - * A {@link Collection} method that returns a collection of all parameter values. - * - * @return A {@link Collection} containing all parameter values. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - protected Collection getParameterValues() { - return parameters.values(); - } - - /** - * A {@code void} method that updates the value of an existing parameter. - * - * @param pKey {@link String} - The key of the parameter to update. - * @param pNewValue {@link Object} - The new value to set for the parameter. - * @throws IllegalArgumentException if the key does not exist. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - protected void updateParameter(String pKey, Object pNewValue) { - if (parameters.containsKey(pKey)) { - parameters.put(pKey, pNewValue); - } else { - throw new IllegalArgumentException("Key does not exist: " + pKey); - } - } -} diff --git a/NeoForge/src/main/java/software/bluelib/event/ReloadEventHandler.java b/NeoForge/src/main/java/software/bluelib/event/ReloadEventHandler.java deleted file mode 100644 index 9a095c52..00000000 --- a/NeoForge/src/main/java/software/bluelib/event/ReloadEventHandler.java +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) BlueLib. Licensed under the MIT License. - -package software.bluelib.event; - -import com.google.gson.JsonParseException; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.MinecraftServer; -import software.bluelib.entity.variant.VariantLoader; - -/** - * A {@code ReloadEventHandler} class responsible for handling events related to reloading entity variants. - *

- * This class includes methods for registering entity variants when the server starts. - *

- *

- * Key Features: - *

    - *
  • {@link #registerEntityVariants(MinecraftServer, String, String, String, String)} - Registers entity variants from specified locations.
  • - *
- *

- * @see VariantLoader - * @see MinecraftServer - * @see ResourceLocation - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ -public class ReloadEventHandler { - - /** - * A {@code void} method that registers entity variants from specified locations. - *

- * This method attempts to load variants from both mod and datapack locations, providing status information - * and handling any exceptions that occur during the loading process. - *

- *

- * Parameters: - *

    - *
  • {@code pServer} - The server instance of the current world.
  • - *
  • {@code pEntityName} - The entity name to load.
  • - *
  • {@code pModID} - The mod ID used to locate the entity variant resources. (Use your Mod's ID)
  • - *
  • {@code pModPathLocation} - The path location within the mod where variants are stored.
  • - *
  • {@code pDataPathLocation} - The path location within the resource pack where variants are stored.
  • - *
- *

- *

- * Exception Handling: - *

    - *
  • {@code JsonParseException} - Thrown when there is an error parsing the JSON files.
  • - *
  • {@code RuntimeException} - Thrown for unexpected errors during the registration process.
  • - *
- *

- * @param pServer {@link MinecraftServer} - The server instance of the current world. - * @param pEntityName {@link String} - The entity name to load. - * @param pModID {@link String} - The mod ID used to locate the entity variant resources. (Use your Mod's ID) - * @param pModPathLocation {@link String} - The path location within the mod where variants are stored. - * @param pDataPathLocation {@link String} - The path location within the datapack where variants are stored. - * @throws JsonParseException if there is an error parsing the JSON files. - * @throws RuntimeException if an unexpected error occurs during the registration process. - * @see MinecraftServer - * @see ResourceLocation - * @see VariantLoader - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - protected static void registerEntityVariants(MinecraftServer pServer, String pEntityName, String pModID, String pModPathLocation, String pDataPathLocation) { - ResourceLocation modLocation = new ResourceLocation(pModID, pModPathLocation); - ResourceLocation dataLocation = new ResourceLocation(pModID, pDataPathLocation); - try { - VariantLoader.loadVariants(modLocation, dataLocation, pServer, pEntityName); - } catch (JsonParseException pException) { - throw new RuntimeException("Failed to parse JSON(s) while registering entity variants for " + pEntityName + " from Mod with ModID: " + pModID, pException); - } catch (Exception pException) { - throw new RuntimeException("Unexpected error occurred while registering entity variants for " + pEntityName + " from Mod with ModID: " + pModID, pException); - } - } -} diff --git a/NeoForge/src/main/java/software/bluelib/example/entity/dragon/DragonModel.java b/NeoForge/src/main/java/software/bluelib/example/entity/dragon/DragonModel.java deleted file mode 100644 index 67091901..00000000 --- a/NeoForge/src/main/java/software/bluelib/example/entity/dragon/DragonModel.java +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) BlueLib. Licensed under the MIT License. - -package software.bluelib.example.entity.dragon; - -import net.minecraft.resources.ResourceLocation; -import software.bernie.geckolib.model.GeoModel; -import software.bluelib.BlueLib; - -public class DragonModel extends GeoModel { - - - // Get the Model Location - @Override - public ResourceLocation getModelResource(DragonEntity pObject) { - return new ResourceLocation(BlueLib.MODID, "geo/dragon.geo.json"); - } - - // Get the Texture Location - @Override - public ResourceLocation getTextureResource(DragonEntity pObject) { - return pObject.getTextureLocation(BlueLib.MODID, "textures/entity/" + pObject.entityName + "/" + pObject.getVariantName() + ".png"); - } - - // Get the Animation Location - @Override - public ResourceLocation getAnimationResource(DragonEntity pAnimatable) { - return new ResourceLocation(BlueLib.MODID, "animations/dragon.animation.json"); - } -} diff --git a/NeoForge/src/main/java/software/bluelib/example/entity/rex/RexModel.java b/NeoForge/src/main/java/software/bluelib/example/entity/rex/RexModel.java deleted file mode 100644 index 6084892c..00000000 --- a/NeoForge/src/main/java/software/bluelib/example/entity/rex/RexModel.java +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) BlueLib. Licensed under the MIT License. - -package software.bluelib.example.entity.rex; - -import net.minecraft.resources.ResourceLocation; -import software.bernie.geckolib.model.GeoModel; -import software.bluelib.BlueLib; - -public class RexModel extends GeoModel { - - - // Get the Model Location - @Override - public ResourceLocation getModelResource(RexEntity pObject) { - return new ResourceLocation(BlueLib.MODID, "geo/rex.geo.json"); - } - - // Get the Texture Location - @Override - public ResourceLocation getTextureResource(RexEntity pObject) { - return pObject.getTextureLocation(BlueLib.MODID, "textures/entity/" + pObject.entityName + "/" + pObject.getVariantName() + ".png"); - } - - // Get the Animation Location - @Override - public ResourceLocation getAnimationResource(RexEntity pAnimatable) { - return new ResourceLocation(BlueLib.MODID, "animations/rex.animation.json"); - } -} diff --git a/NeoForge/src/main/java/software/bluelib/example/event/ClientEvents.java b/NeoForge/src/main/java/software/bluelib/example/event/ClientEvents.java deleted file mode 100644 index 8d3a63de..00000000 --- a/NeoForge/src/main/java/software/bluelib/example/event/ClientEvents.java +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) BlueLib. Licensed under the MIT License. - -package software.bluelib.example.event; - -import net.neoforged.bus.api.SubscribeEvent; -import net.neoforged.neoforge.client.event.EntityRenderersEvent; -import net.neoforged.neoforge.event.entity.EntityAttributeCreationEvent; -import software.bluelib.example.entity.dragon.DragonEntity; -import software.bluelib.example.entity.dragon.DragonRender; -import software.bluelib.example.entity.rex.RexEntity; -import software.bluelib.example.entity.rex.RexRender; -import software.bluelib.example.init.ModEntities; - -public class ClientEvents { - @SubscribeEvent - public static void registerRenderers(final EntityRenderersEvent.RegisterRenderers pEvent) { - // Register the renderer for all the Entities - pEvent.registerEntityRenderer(ModEntities.DRAGON.get(), DragonRender::new); - pEvent.registerEntityRenderer(ModEntities.REX.get(), RexRender::new); - } - - // Register the Attributes - @SubscribeEvent - public static void registerAttributes(EntityAttributeCreationEvent pEvent) { - pEvent.put(ModEntities.DRAGON.get(), DragonEntity.createAttributes().build()); - pEvent.put(ModEntities.REX.get(), RexEntity.createAttributes().build()); - } -} diff --git a/NeoForge/src/main/java/software/bluelib/exception/CouldNotLoadJSON.java b/NeoForge/src/main/java/software/bluelib/exception/CouldNotLoadJSON.java deleted file mode 100644 index c93d1015..00000000 --- a/NeoForge/src/main/java/software/bluelib/exception/CouldNotLoadJSON.java +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) BlueLib. Licensed under the MIT License. - -package software.bluelib.exception; - -/** - * A {@code RuntimeException} that represents an error when a JSON file could not be loaded. - *

- * This exception provides additional context by including the {@link #getResourceId()} of the JSON file that failed to load. - *

- *

- * Key Features: - *

    - *
  • {@link #getResourceId()} - Retrieves the ID of the resource that could not be loaded.
  • - *
- *

- * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ -public class CouldNotLoadJSON extends RuntimeException { - - /** - * A {@link String} that represents the ID of the resource that could not be loaded. - *

- * This ID is used to provide additional context for the error. - *

- * @Co-author MeAlam, Dan - * @since 1.0.0 - */ - private final String resourceId; - - /** - * Constructs a new {@code CouldNotLoadJSON} exception with the specified detail message and resource ID. - *

- * The detail message provides information about the nature of the error, while the resource ID indicates - * which specific resource could not be loaded. - *

- * - * @param pMessage {@link String} - The detail message explaining the reason for the exception. - * @param pResourceId {@link String} - The ID of the resource that could not be loaded. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - public CouldNotLoadJSON(String pMessage, String pResourceId) { - super(pMessage); - this.resourceId = pResourceId; - } - - /** - * A {@link String} that retrieves the resource ID of the JSON file that could not be loaded. - *

- * This method provides access to the ID of the resource that caused the exception. - *

- * - * @return The resource ID as a {@link String}. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - public String getResourceId() { - return resourceId; - } -} diff --git a/NeoForge/src/main/java/software/bluelib/utils/ParameterUtils.java b/NeoForge/src/main/java/software/bluelib/utils/ParameterUtils.java deleted file mode 100644 index 3994cdf5..00000000 --- a/NeoForge/src/main/java/software/bluelib/utils/ParameterUtils.java +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright (c) BlueLib. Licensed under the MIT License. - -package software.bluelib.utils; - -import software.bluelib.entity.variant.VariantParameter; -import software.bluelib.entity.variant.VariantLoader; - -import java.util.HashMap; -import java.util.Map; -import java.util.NoSuchElementException; - -/** - * A {@code Class} for managing custom parameters associated with entity variants. - *

- * This class provides methods to retrieve custom parameters for variants and allows - * for building and connecting parameters to specific variants using the {@link ParameterBuilder}. - *

- *

- * Key Methods: - *

    - *
  • {@link #getParameter(String, String)} - Retrieves the value of a custom parameter for a specific variant.
  • - *
- *

- * Nested Classes: - *
    - *
  • {@link ParameterBuilder} - A builder class for creating and associating custom parameters with a specific variant.
  • - *
- *

- * @since 1.0.0 - * @author MeAlam - * @Co-author Dan - */ -public class ParameterUtils { - - /** - * A {@link Map} holding custom parameters for each variant. - *

- * The outer map's key is the variant's name, and the inner map contains key-value pairs - * of custom parameters for that variant. - *

- * @Co-author MeAlam, Dan - * @since 1.0.0 - */ - private static final Map> variantParametersMap = new HashMap<>(); - - /** - * Retrieves the value of a custom parameter for a specific variant. - *

- * If the parameter is not found, "null" is returned. - *

- * - * @param pVariantName {@link String} - The name of the variant. - * @param pParameterKey {@link String} - The key of the parameter to retrieve. - * @return The value of the custom parameter for the specified variant. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - public static String getParameter(String pVariantName, String pParameterKey) { - return variantParametersMap.getOrDefault(pVariantName, new HashMap<>()).getOrDefault(pParameterKey, "null"); - } - - /** - * A {@code Builder} class for creating and associating custom parameters with a specific variant. - *

- * This class allows chaining methods to build and connect parameters to a variant. - *

- * - *

- * Key Methods: - *

    - *
  • {@link #forVariant(String, String)} - Creates a new instance of {@link ParameterBuilder} for the specified entity and variant.
  • - *
  • {@link #withParameter(String)} - Adds a parameter to the parameters map with a default value of "null".
  • - *
  • {@link #connect()} - Adds parameters to the variant and updates the static {@link VariantParameter} with these parameters.
  • - *
- *

- *

- * **Note:** The "null" value is used only if the parameter is not specified in the JSON files. - *

- * @since 1.0.0 - * @author MeAlam - * @Co-author Dan - */ - public static class ParameterBuilder { - /** - * The name of the variant for which parameters are being built. - * @Co-author MeAlam, Dan - * @since 1.0.0 - */ - private final String variantName; - - /** - * The name of the entity for which parameters are being built. - * @Co-author MeAlam, Dan - * @since 1.0.0 - */ - private final String entityName; - - /** - * A {@link Map} to store parameters for the variant. - *

- * Each key-value pair represents a parameter name and its default value. - *

- * @Co-author MeAlam, Dan - * @since 1.0.0 - */ - private final Map parameters = new HashMap<>(); - - /** - * Constructor to initialize the builder with a specific entity name and variant name. - * - * @param pEntityName {@link String} - The name of the entity. - * @param pVariantName {@link String} - The name of the variant. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - private ParameterBuilder(String pEntityName, String pVariantName) { - this.variantName = pVariantName; - this.entityName = pEntityName; - } - - /** - * Creates a new instance of {@link ParameterBuilder} for the specified entity and variant. - * - * @param pEntityName {@link String} - The name of the entity. - * @param pVariantName {@link String} - The name of the variant. - * @return A new instance of {@link ParameterBuilder}. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - public static ParameterBuilder forVariant(String pEntityName, String pVariantName) { - return new ParameterBuilder(pEntityName, pVariantName); - } - - /** - * Adds a parameter to the parameters map with a default value of "null".
- *

- * **Note:** The "null" value is used only if the parameter is not specified in the JSON files. - *

- * - * @param pParameter {@link String} - The key of the parameter to add. - * @return The current instance of {@link ParameterBuilder} for method chaining. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - public ParameterBuilder withParameter(String pParameter) { - parameters.put(pParameter, "null"); - return this; - } - - /** - * Adds a parameter to the parameters map with a specified default value. - *

- * Connects the parameters built with this builder to the specified variant. - * Updates the static {@link VariantParameter} with the parameters for the specified variant. - *

- * - * @return The current instance of {@link ParameterBuilder} for method chaining. - * @throws NoSuchElementException if the specified variant is not found for the entity. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 - */ - public ParameterBuilder connect() { - VariantParameter variant = VariantLoader.getVariantByName(entityName, variantName); - if (variant != null) { - Map updatedParameters = new HashMap<>(); - for (String key : parameters.keySet()) { - updatedParameters.put(key, variant.getParameter(key)); - } - variantParametersMap.put(variantName, updatedParameters); - } else { - throw new NoSuchElementException("Variant '" + variantName + "' not found for entity '" + entityName + "'"); - } - return this; - } - } -} diff --git a/NeoForge/src/main/resources/META-INF/mods.toml b/NeoForge/src/main/resources/META-INF/mods.toml deleted file mode 100644 index 61897870..00000000 --- a/NeoForge/src/main/resources/META-INF/mods.toml +++ /dev/null @@ -1,31 +0,0 @@ -modLoader="javafml" -loaderVersion="${loader_version_range}" -license="${mod_license}" -issueTrackerURL="https://github.com/MeAlam1/BlueLib/issues" -[[mods]] -modId="${mod_id}" -version="${mod_version}" -displayName="${mod_name}" - -# A URL to query for updates for this mod. See the JSON update specification https://docs.neoforged.net/docs/misc/updatechecker/ -#updateJSONURL="https://change.me.example.invalid/updates.json" #optional -displayURL="https://github.com/MeAlam1/BlueLib/wiki" -#logoFile="examplemod.png" -credits="Anyone who contributed to the Source Code of BlueLib!" -authors="${mod_authors}" - - -description='''${mod_description}''' -[[dependencies.${mod_id}]] - modId="neoforge" - type="required" - versionRange="${neo_version_range}" - ordering="NONE" - side="BOTH" - -[[dependencies.${mod_id}]] - modId="minecraft" - type="required" - versionRange="${minecraft_version_range}" - ordering="NONE" - side="BOTH" \ No newline at end of file diff --git a/NeoForge/src/main/resources/pack.mcmeta b/NeoForge/src/main/resources/pack.mcmeta deleted file mode 100644 index 325d954f..00000000 --- a/NeoForge/src/main/resources/pack.mcmeta +++ /dev/null @@ -1,6 +0,0 @@ -{ - "pack": { - "pack_format": 9, - "description": "bluelib" - } -} \ No newline at end of file diff --git a/README.md b/README.md index 618c966c..b46c34a1 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ Neoforge Forge + Fabric @@ -29,6 +30,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +
VersionSupport
1.16.5Active
1.18.2Active
1.20.xActive
1.21.xActive
+ @@ -65,4 +94,9 @@

Discord

+

+ BlueLib Licence  + BlueLib Release  + BlueLib Commit Activity  +

diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..bfcac3d0 --- /dev/null +++ b/build.gradle @@ -0,0 +1,94 @@ +plugins { + // Required for NeoGradle + id "org.jetbrains.gradle.plugin.idea-ext" version "1.1.7" +} + +subprojects { + apply plugin: 'java' + + java.toolchain.languageVersion = JavaLanguageVersion.of(17) + java.withSourcesJar() + java.withJavadocJar() + + jar { + from(rootProject.file("LICENSE")) { + rename { "${it}_${mod_name}" } + } + manifest { + attributes([ + 'Specification-Title' : mod_name, + 'Specification-Vendor' : mod_author, + 'Specification-Version' : project.jar.archiveVersion, + 'Implementation-Title' : project.name, + 'Implementation-Version' : project.jar.archiveVersion, + 'Implementation-Vendor' : mod_author, + 'Implementation-Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"), + 'Timestamp' : System.currentTimeMillis(), + 'Built-On-Java' : "${System.getProperty('java.vm.version')} (${System.getProperty('java.vm.vendor')})", + 'Built-On-Minecraft' : minecraft_version + ]) + } + } + + sourcesJar { + from(rootProject.file("LICENSE")) { + rename { "${it}_${mod_name}" } + } + } + + repositories { + mavenCentral() + maven { + name = 'Sponge / Mixin' + url = 'https://repo.spongepowered.org/repository/maven-public/' + } + maven { + name = 'BlameJared Maven (JEI / CraftTweaker / Bookshelf)' + url = 'https://maven.blamejared.com' + } + maven { + url "https://cursemaven.com" + } + } + + tasks.withType(JavaCompile).configureEach { + + it.options.encoding = 'UTF-8' + it.options.getRelease().set(17) + } + + processResources { + def expandProps = [ + "version": version, + "group": project.group, //Else we target the task's group. + "minecraft_version": minecraft_version, + "forge_version": forge_version, + "forge_loader_version_range": forge_loader_version_range, + "forge_version_range": forge_version_range, + "minecraft_version_range": minecraft_version_range, + "fabric_version": fabric_version, + "fabric_loader_version": fabric_loader_version, + "mod_name": mod_name, + "mod_author": mod_author, + "mod_id": mod_id, + "license": license, + "description": project.description, + "neoforge_version": neoforge_version, + "neoforge_loader_version_range": neoforge_loader_version_range, + "credits": credits + ] + + filesMatching(['pack.mcmeta', 'fabric.mod.json', 'META-INF/mods.toml', '*.mixins.json']) { + expand expandProps + } + inputs.properties(expandProps) + } + + // Disables Gradle's custom module metadata from being published to maven. The + // metadata includes mapped dependencies which are not reasonably consumable by + // other mod developers. + tasks.withType(GenerateModuleMetadata).configureEach { + + enabled = false + } +} diff --git a/common/build.gradle b/common/build.gradle new file mode 100644 index 00000000..6a1f6832 --- /dev/null +++ b/common/build.gradle @@ -0,0 +1,39 @@ +plugins { + id 'idea' + id 'java' + id 'maven-publish' + id 'org.spongepowered.gradle.vanilla' version '0.2.1-SNAPSHOT' +} +base { + archivesName = "${mod_name}-common-${minecraft_version}" +} +repositories { + maven { + url "https://cursemaven.com" + } +} +minecraft { + version(minecraft_version) + if(file("src/main/resources/${mod_id}.accesswidener").exists()){ + accessWideners(file("src/main/resources/${mod_id}.accesswidener")) + } +} + +dependencies { + compileOnly group:'org.spongepowered', name:'mixin', version:'0.8.5-SNAPSHOT' + implementation group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2' +} + +publishing { + publications { + mavenJava(MavenPublication) { + artifactId base.archivesName.get() + from components.java + } + } + repositories { + maven { + url "file://" + System.getenv("local_maven") + } + } +} \ No newline at end of file diff --git a/common/src/main/java/software/bluelib/BlueLibCommon.java b/common/src/main/java/software/bluelib/BlueLibCommon.java new file mode 100644 index 00000000..6dac3dd8 --- /dev/null +++ b/common/src/main/java/software/bluelib/BlueLibCommon.java @@ -0,0 +1,108 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib; + +import software.bluelib.interfaces.platform.IPlatformHelper; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +import java.util.ServiceLoader; +import java.util.concurrent.TimeUnit; + +import static software.bluelib.BlueLibConstants.SCHEDULER; + +/** + * A {@code public class} responsible for common initialization logic and platform detection. + *

+ * This class contains utility methods to load services, initialize the mod in developer mode, + * and determine if the current environment is a development environment. + *

+ *

+ * Key Methods: + *

    + *
  • {@link #init()} - Initializes BlueLib and logs welcome messages if in developer mode.
  • + *
  • {@link #isDeveloperMode()} - Checks if the mod is running in developer mode.
  • + *
+ * + * @author MeAlam + * @since 1.0.0 + */ +public class BlueLibCommon { + + /** + * Private constructor to prevent instantiation. + *

+ * This constructor is intentionally empty to prevent creating instances of this class. + *

+ * + * @author MeAlam + * @since 1.0.0 + */ + private BlueLibCommon() { + } + + /** + * A {@code public static final} {@link IPlatformHelper} instance that represents the current platform helper loaded for the mod. + * This is used to identify platform-specific functionalities. + * + * @since 1.0.0 + */ + public static final IPlatformHelper PLATFORM = load(IPlatformHelper.class); + + /** + * A {@code public static} {@link T} that loads a service using {@link ServiceLoader}. + * + * @param pClazz {@link Class} - The class type of the service to load. + * @param The type of the service class to be loaded. + * @return The loaded service instance of type {@code T}. + * @throws NullPointerException if the service cannot be found. + * @author MeAlam + * @since 1.0.0 + */ + public static T load(Class pClazz) { + return ServiceLoader.load(pClazz) + .findFirst() + .orElseThrow(() -> new NullPointerException("Failed to load service for " + pClazz.getName())); + } + + /** + * A {@code public static void} that initializes BlueLib and logs a thank-you message if in developer mode.
+ * The message is scheduled to appear after 5 seconds and then the scheduler shuts down. + * + * @author MeAlam + * @since 1.0.0 + */ + public static void init() { + if (isDeveloperMode()) { + SCHEDULER.schedule(() -> { + BaseLogger.logBlueLib("**************************************************"); + BaseLogger.logBlueLib(" "); + BaseLogger.logBlueLib(" Thank you for using BlueLib! "); + BaseLogger.logBlueLib(" We appreciate your support. "); + BaseLogger.logBlueLib(" "); + BaseLogger.logBlueLib("**************************************************"); + SCHEDULER.shutdown(); + }, 5, TimeUnit.SECONDS); + } + } + + /** + * A {@code public static} {@link Boolean} that checks if the mod is running in developer mode. + *

+ * Developer mode is active when the mod is not running in a production environment. + *

+ * + * @return {@code true} if running in developer mode, {@code false} if it isn't. + * @author MeAlam + * @since 1.0.0 + */ + public static boolean isDeveloperMode() { + boolean isDevMode = PLATFORM.isDevelopmentEnvironment(); + if (isDevMode) { + BaseLogger.log(BaseLogLevel.INFO, "Running in Developer mode.", true); + } else { + BaseLogger.log(BaseLogLevel.INFO, "Running in Production mode.", true); + } + return isDevMode; + } +} diff --git a/common/src/main/java/software/bluelib/BlueLibConstants.java b/common/src/main/java/software/bluelib/BlueLibConstants.java new file mode 100644 index 00000000..0950db7a --- /dev/null +++ b/common/src/main/java/software/bluelib/BlueLibConstants.java @@ -0,0 +1,93 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.logging.Logger; + +/** + * A {@code public class} that defines common constants used across the BlueLib mod. + *

+ * This class contains constants such as the mod's {@link #MOD_ID}, {@link #MOD_NAME}, and a + * {@link ScheduledExecutorService} for scheduling tasks. + *

+ *

+ * Key Fields: + *

    + *
  • {@link #SCHEDULER} - Executor for scheduling delayed tasks.
  • + *
  • {@link #MOD_ID} - Unique identifier for the mod.
  • + *
  • {@link #MOD_NAME} - Display name of the mod.
  • + *
+ * + * @author MeAlam + * @since 1.0.0 + */ +public class BlueLibConstants { + + /** + * Private constructor to prevent instantiation. + *

+ * This constructor is intentionally empty to prevent creating instances of this class. + *

+ * + * @author MeAlam + * @since 1.0.0 + */ + private BlueLibConstants() { + } + + /** + * A {@link Logger} instance for logging messages. + * + * @since 1.0.0 + */ + public static final Logger LOGGER = Logger.getLogger(BlueLibConstants.MOD_NAME); + + /** + * A {@code public static final} {@link ScheduledExecutorService} used to schedule tasks, such as printing messages after a delay. + *

+ * This executor runs tasks on a single thread to ensure delayed tasks run in a separate thread from the main thread. + *

+ * + * @since 1.0.0 + */ + public static final ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(1); + + /** + * A {@code public static final} {@link String} representing the Mod ID for the {@code BlueLib} mod. + *

This serves as a unique identifier for the mod.

+ * + * @since 1.0.0 + */ + public static final String MOD_ID = "bluelib"; + + /** + * A {@code public static final} {@link String} representing the Mod Name for the {@code BlueLib} mod. + * + * @since 1.0.0 + */ + public static final String MOD_NAME = "BlueLib"; + + /** TODO: Always have on False when pushing to production + * A {@code public static final} {@link Boolean} indicating whether the example features should be enabled.
+ * Should always be false in production. + * + * @since 1.0.0 + */ + public static final Boolean isExampleEnabled = true; + + /** TODO: Always have on False when pushing to production + * A {@link Boolean} to enable or disable BlueLib specific logging. + * + * @since 1.0.0 + */ + public static boolean isBlueLibLoggingEnabled = true; + + /** TODO: Always have on False when pushing to production + * A {@link Boolean} to enable or disable general logging. + * + * @since 1.0.0 + */ + public static boolean isLoggingEnabled = true; +} \ No newline at end of file diff --git a/common/src/main/java/software/bluelib/interfaces/logging/ILogColorProvider.java b/common/src/main/java/software/bluelib/interfaces/logging/ILogColorProvider.java new file mode 100644 index 00000000..b029f0cc --- /dev/null +++ b/common/src/main/java/software/bluelib/interfaces/logging/ILogColorProvider.java @@ -0,0 +1,36 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.interfaces.logging; + +import java.util.logging.Level; + +/** + * A {@code public Interface} for providing color codes based on log levels. + *

+ * This interface defines a method to retrieve color codes for various log levels. Implementations should provide + * the appropriate color codes for each log level to enhance the readability of log messages. + *

+ *

+ * Key Methods: + *

    + *
  • {@link #getColor(Level)} - Retrieves the color code associated with a specific {@link Level} of logging.
  • + *
+ * + * @author MeAlam + * @since 1.0.0 + */ +public interface ILogColorProvider { + + /** + * A {@link String} that provides the color code for the specified {@link Level}. + *

+ * Implementations should return a color code suitable for displaying log messages with the given log level. + *

+ * + * @param pLevel {@link Level} - The log level for which to retrieve the color code. + * @return The color code as a {@link String} for the specified log level. + * @author MeAlam + * @since 1.0.0 + */ + String getColor(Level pLevel); +} diff --git a/common/src/main/java/software/bluelib/interfaces/platform/IPlatformHelper.java b/common/src/main/java/software/bluelib/interfaces/platform/IPlatformHelper.java new file mode 100644 index 00000000..6938a537 --- /dev/null +++ b/common/src/main/java/software/bluelib/interfaces/platform/IPlatformHelper.java @@ -0,0 +1,65 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.interfaces.platform; + +/** + * A {@code public interface} that defines platform-specific functionality for the BlueLib mod. + *

+ * This interface provides methods to retrieve the platform name, check for loaded mods, and determine + * if the game is running in a development environment. + *

+ *

+ * Key Methods: + *

    + *
  • {@link #getPlatformName()} - Retrieves the name of the current platform.
  • + *
  • {@link #isModLoaded(String)} - Checks if a mod is loaded based on its ID.
  • + *
  • {@link #isDevelopmentEnvironment()} - Determines if the environment is for development.
  • + *
  • {@link #getEnvironmentName()} - Retrieves the name of the environment type.
  • + *
+ * + * @author MeAlam + * @since 1.0.0 + */ +public interface IPlatformHelper { + + /** + * A {@link String} method that retrieves the name of the current platform. + * + * @return The name of the current platform as a {@link String}. + * @since 1.0.0 + */ + String getPlatformName(); + + /** + * A {@link Boolean} method that checks if a mod with the given ID is loaded. + * + * @param pModId {@link String} - The ID of the mod to check. + * @return {@code true} if the mod is loaded, {@code false} if it isn't. + * @since 1.0.0 + */ + boolean isModLoaded(String pModId); + + /** + * A {@link Boolean} method that checks if the game is currently running in a development environment. + * + * @return {@code true} if running in a development environment, {@code false} if it isn't. + * @author MeAlam + * @since 1.0.0 + */ + boolean isDevelopmentEnvironment(); + + /** + * A {@link String} method that retrieves the name of the current environment type. + *

+ * The environment type is either "development" or "production" based on whether the game is running + * in a development environment. + *

+ * + * @return {@link String} - The name of the environment type. + * @author MeAlam + * @since 1.0.0 + */ + default String getEnvironmentName() { + return isDevelopmentEnvironment() ? "development" : "production"; + } +} diff --git a/common/src/main/java/software/bluelib/interfaces/variant/IVariantAccessor.java b/common/src/main/java/software/bluelib/interfaces/variant/IVariantAccessor.java new file mode 100644 index 00000000..b688ed28 --- /dev/null +++ b/common/src/main/java/software/bluelib/interfaces/variant/IVariantAccessor.java @@ -0,0 +1,37 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.interfaces.variant; + +/** + * A {@code public Interface} interface responsible for accessing and modifying + * variant-related properties of an entity within the BlueLib framework. + *

+ * Key Methods: + *

    + *
  • {@link #setEntityVariantName(String)} - Sets the variant name for an entity.
  • + *
  • {@link #getEntityVariantName()} - Retrieves the variant name of an entity.
  • + *
+ * + * @author MeAlam + * @since 1.0.0 + */ +public interface IVariantAccessor { + + /** + * A {@code void} method that sets the variant name for the entity. + * + * @param pVariantName {@link String} - The variant name to assign to the entity. + * @author MeAlam + * @since 1.0.0 + */ + void setEntityVariantName(String pVariantName); + + /** + * A {@link String} method that retrieves the variant name of the entity. + * + * @return {@link String} - The current variant name of the entity. + * @author MeAlam + * @since 1.0.0 + */ + String getEntityVariantName(); +} diff --git a/common/src/main/java/software/bluelib/utils/conversion/CaseConverterUtils.java b/common/src/main/java/software/bluelib/utils/conversion/CaseConverterUtils.java new file mode 100644 index 00000000..725ef360 --- /dev/null +++ b/common/src/main/java/software/bluelib/utils/conversion/CaseConverterUtils.java @@ -0,0 +1,268 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.utils.conversion; + +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +/** + * A {@code public class} for converting strings between various naming conventions: + * camelCase, PascalCase, snake_case, kebab-case, UPPER_SNAKE_CASE, Train-Case, flatcase, and COBOL-CASE. + *

+ * Key Methods: + *

    + *
  • {@link #toCamelCase(String)} - Converts input to camelCase.
  • + *
  • {@link #toPascalCase(String)} - Converts input to PascalCase.
  • + *
  • {@link #toSnakeCase(String)} - Converts input to snake_case.
  • + *
  • {@link #toKebabCase(String)} - Converts input to kebab-case.
  • + *
  • {@link #toUpperSnakeCase(String)} - Converts input to UPPER_SNAKE_CASE.
  • + *
  • {@link #toTrainCase(String)} - Converts input to Train-Case.
  • + *
  • {@link #toFlatcase(String)} - Converts input to flatcase.
  • + *
  • {@link #toCobolCase(String)} - Converts input to COBOL-CASE.
  • + *
+ * + * @author MeAlam + * @since 1.0.0 + */ +public class CaseConverterUtils { + + /** + * Private constructor to prevent instantiation. + *

+ * This constructor is intentionally empty to prevent creating instances of this utility class. + *

+ * + * @author MeAlam + * @since 1.0.0 + */ + private CaseConverterUtils() { + } + + /** + * A {@link String} that converts a given {@link String} to camelCase. + *

+ * If the input is in PascalCase, snake_case, or kebab-case, it will be converted accordingly. + * + * @param pInput {@link String} - The input string to be converted. + * @return The camelCase version of the input string. + * @author MeAlam + * @since 1.0.0 + */ + public static String toCamelCase(String pInput) { + if (pInput == null || pInput.isEmpty()) { + BaseLogger.log(BaseLogLevel.INFO, "Input for toCamelCase is null or empty.", true); + return pInput; + } + + if (Character.isUpperCase(pInput.charAt(0)) && !pInput.contains("_") && !pInput.contains("-")) { + BaseLogger.log(BaseLogLevel.INFO, "Input detected as PascalCase.", true); + return pInput.substring(0, 1).toLowerCase() + pInput.substring(1); + } + + if (pInput.contains("_")) { + BaseLogger.log(BaseLogLevel.INFO, "Input detected as snake_case.", true); + return convertUsingDelimiter(pInput, "_", true); + } + + if (pInput.contains("-")) { + BaseLogger.log(BaseLogLevel.INFO, "Input detected as kebab-case.", true); + return convertUsingDelimiter(pInput, "-", true); + } + + BaseLogger.log(BaseLogLevel.ERROR, "Input case is not recognized.", true); + return pInput; + } + + /** + * A {@link String} that converts a given {@link String} to PascalCase. + *

+ * If the input is in camelCase, snake_case, or kebab-case, it will be converted accordingly. + * + * @param pInput {@link String} - The input string to be converted. + * @return The PascalCase version of the input string. + * @author MeAlam + * @since 1.0.0 + */ + public static String toPascalCase(String pInput) { + if (pInput == null || pInput.isEmpty()) { + BaseLogger.log(BaseLogLevel.WARNING, "Input for toPascalCase is null or empty.", true); + return pInput; + } + + if (!pInput.contains("_") && !pInput.contains("-") && Character.isLowerCase(pInput.charAt(0))) { + BaseLogger.log(BaseLogLevel.INFO, "Input detected as camelCase.", true); + return pInput.substring(0, 1).toUpperCase() + pInput.substring(1); + } + + if (pInput.contains("_")) { + BaseLogger.log(BaseLogLevel.INFO, "Input detected as snake_case.", true); + return convertUsingDelimiter(pInput, "_", false); + } + + if (pInput.contains("-")) { + BaseLogger.log(BaseLogLevel.INFO, "Input detected as kebab-case.", true); + return convertUsingDelimiter(pInput, "-", false); + } + + BaseLogger.log(BaseLogLevel.ERROR, "Input case is not recognized.", true); + return pInput; + } + + /** + * A {@link String} that converts a given {@link String} to snake_case. + *

+ * It converts camelCase, PascalCase, and kebab-case to snake_case by adding underscores where appropriate. + * + * @param pInput {@link String} - The input string to be converted. + * @return The snake_case version of the input string. + * @author MeAlam + * @since 1.0.0 + */ + public static String toSnakeCase(String pInput) { + if (pInput == null || pInput.isEmpty()) { + BaseLogger.log(BaseLogLevel.WARNING, "Input for toSnakeCase is null or empty.", true); + return pInput; + } + + String result = pInput.replaceAll("([a-z])([A-Z])", "$1_$2"); + result = result.toLowerCase(); + result = result.replace("-", "_"); + + BaseLogger.log(BaseLogLevel.SUCCESS, "Converted to snake_case: " + result, true); + return result; + } + + /** + * A {@link String} that converts a given {@link String} to kebab-case. + *

+ * It converts camelCase, PascalCase, and snake_case to kebab-case by adding hyphens where appropriate. + * + * @param pInput {@link String} - The input string to be converted. + * @return The kebab-case version of the input string. + * @author MeAlam + * @since 1.0.0 + */ + public static String toKebabCase(String pInput) { + if (pInput == null || pInput.isEmpty()) { + BaseLogger.log(BaseLogLevel.WARNING, "Input for toKebabCase is null or empty.", true); + return pInput; + } + + String result = pInput.replaceAll("([a-z])([A-Z])", "$1-$2"); + result = result.toLowerCase(); + result = result.replace("_", "-"); + + BaseLogger.log(BaseLogLevel.SUCCESS, "Converted to kebab-case: " + result, true); + return result; + } + + /** + * A {@link String} that converts a given {@link String} to UPPER_SNAKE_CASE. + *

+ * Converts camelCase, PascalCase, snake_case, and kebab-case to UPPER_SNAKE_CASE by adding underscores + * and converting all letters to uppercase. + * + * @param pInput {@link String} - The input string to be converted. + * @return The UPPER_SNAKE_CASE version of the input string. + * @author MeAlam + * @since 1.0.0 + */ + public static String toUpperSnakeCase(String pInput) { + if (pInput == null || pInput.isEmpty()) { + BaseLogger.log(BaseLogLevel.WARNING, "Input for toUpperSnakeCase is null or empty.", true); + return pInput; + } + + String result = toSnakeCase(pInput); + return result.toUpperCase(); + } + + /** + * A {@link String} that converts a given {@link String} to Train-Case. + *

+ * Converts camelCase, PascalCase, snake_case, and kebab-case to Train-Case by adding hyphens and + * capitalizing each word. + * + * @param pInput {@link String} - The input string to be converted. + * @return The Train-Case version of the input string. + * @author MeAlam + * @since 1.0.0 + */ + public static String toTrainCase(String pInput) { + if (pInput == null || pInput.isEmpty()) { + BaseLogger.log(BaseLogLevel.WARNING, "Input for toTrainCase is null or empty.", true); + return pInput; + } + + String result = toKebabCase(pInput).replace("-", " "); + return toCamelCase(result).replace(" ", "-"); + } + + /** + * A {@link String} that converts a given {@link String} to flatcase. + *

+ * Converts all cases to flatcase by removing any delimiters and converting to lowercase. + * + * @param pInput {@link String} - The input string to be converted. + * @return The flatcase version of the input string. + * @author MeAlam + * @since 1.0.0 + */ + public static String toFlatcase(String pInput) { + if (pInput == null || pInput.isEmpty()) { + BaseLogger.log(BaseLogLevel.WARNING, "Input for toFlatcase is null or empty.", true); + return pInput; + } + + return pInput.replaceAll("[_-]", "").toLowerCase(); + } + + /** + * A {@link String} that converts a given {@link String} to COBOL-CASE. + *

+ * Converts camelCase, PascalCase, snake_case, and kebab-case to COBOL-CASE by making all letters uppercase + * and replacing spaces with hyphens. + * + * @param pInput {@link String} - The input string to be converted. + * @return The COBOL-CASE version of the input string. + * @author MeAlam + * @since 1.0.0 + */ + public static String toCobolCase(String pInput) { + if (pInput == null || pInput.isEmpty()) { + BaseLogger.log(BaseLogLevel.WARNING, "Input for toCobolCase is null or empty.", true); + return pInput; + } + + String result = toKebabCase(pInput); + return result.toUpperCase(); + } + + /** + * A helper method that converts strings using a specified delimiter. + *

+ * This method capitalizes the first letter of each word and joins them using the specified delimiter. + * + * @param pInput {@link String} - The input string to be converted. + * @param pDelim {@link String} - The delimiter used to split the input string. + * @param pCamel {@code boolean} - Indicates if the conversion is to camelCase. + * @return The converted string. + * @author MeAlam + * @since 1.0.0 + */ + private static String convertUsingDelimiter(String pInput, String pDelim, boolean pCamel) { + String[] parts = pInput.split(pDelim); + StringBuilder sb = new StringBuilder(); + + for (String part : parts) { + if (pCamel && sb.isEmpty()) { + sb.append(part.substring(0, 1).toLowerCase()); + } else { + sb.append(part.substring(0, 1).toUpperCase()); + } + sb.append(part.substring(1).toLowerCase()); + } + + return sb.toString(); + } +} diff --git a/common/src/main/java/software/bluelib/utils/conversion/MathConverterUtils.java b/common/src/main/java/software/bluelib/utils/conversion/MathConverterUtils.java new file mode 100644 index 00000000..69275a6f --- /dev/null +++ b/common/src/main/java/software/bluelib/utils/conversion/MathConverterUtils.java @@ -0,0 +1,151 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.utils.conversion; + +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * A {@code class} providing methods for common unit and date conversions. + *

+ * Key Methods: + *

    + *
  • {@link #inchesToCentimeters(double)} - Converts inches to centimeters.
  • + *
  • {@link #centimetersToInches(double)} - Converts centimeters to inches.
  • + *
  • {@link #celsiusToFahrenheit(double)} - Converts Celsius to Fahrenheit.
  • + *
  • {@link #fahrenheitToCelsius(double)} - Converts Fahrenheit to Celsius.
  • + *
  • {@link #kilometersToMiles(double)} - Converts kilometers to miles.
  • + *
  • {@link #milesToKilometers(double)} - Converts miles to kilometers.
  • + *
  • {@link #stringToDate(String, String)} - Converts a string to a {@link Date} object.
  • + *
  • {@link #dateToString(Date, String)} - Converts a {@link Date} object to a string.
  • + *
+ * + * @author MeAlam + * @since 1.0.0 + */ +public class MathConverterUtils { + + /** + * Private constructor to prevent instantiation. + *

+ * This constructor is intentionally empty to prevent creating instances of this utility class. + *

+ * + * @author MeAlam + * @since 1.0.0 + */ + private MathConverterUtils() { + } + + /** + * A {@link Double} that converts a length from inches to centimeters. + * + * @param pInches {@link Double} - The length in inches. + * @return The length in centimeters. + * @author MeAlam + * @since 1.0.0 + */ + public static double inchesToCentimeters(double pInches) { + return pInches * 2.54; + } + + /** + * A {@link Double} that converts a length from centimeters to inches. + * + * @param pCentimeters {@link Double} - The length in centimeters. + * @return The length in inches. + * @since 1.0.0 + */ + public static double centimetersToInches(double pCentimeters) { + return pCentimeters / 2.54; + } + + /** + * A {@link Double} that converts a temperature from Celsius to Fahrenheit. + * + * @param pCelsius {@link Double} - The temperature in Celsius. + * @return The temperature in Fahrenheit. + * @author MeAlam + * @since 1.0.0 + */ + public static double celsiusToFahrenheit(double pCelsius) { + return pCelsius * 9 / 5 + 32; + } + + /** + * A {@link Double} that converts a temperature from Fahrenheit to Celsius. + * + * @param pFahrenheit {@link Double} - The temperature in Fahrenheit. + * @return The temperature in Celsius. + * @since 1.0.0 + */ + public static double fahrenheitToCelsius(double pFahrenheit) { + return (pFahrenheit - 32) * 5 / 9; + } + + /** + * A {@link Double} that converts a distance from kilometers to miles. + * + * @param pKilometers {@link Double} - The distance in kilometers. + * @return The distance in miles. + * @author MeAlam + * @since 1.0.0 + */ + public static double kilometersToMiles(double pKilometers) { + return pKilometers * 0.621371; + } + + /** + * A {@link Double} that converts a distance from miles to kilometers. + * + * @param pMiles {@link Double} - The distance in miles. + * @return The distance in kilometers. + * @since 1.0.0 + */ + public static double milesToKilometers(double pMiles) { + return pMiles / 0.621371; + } + + /** + * A {@link Date} that converts a string to a {@link Date} object. + * + * @param pDateStr {@link String} - The date in string format (e.g., "yyyy-MM-dd"). + * @param pFormat {@link String} - The format of the input date string. + * @return The corresponding {@code Date} object. + * @throws ParseException if the string cannot be parsed. + * @author MeAlam + * @since 1.0.0 + */ + public static Date stringToDate(String pDateStr, String pFormat) throws ParseException { + try { + SimpleDateFormat formatter = new SimpleDateFormat(pFormat); + return formatter.parse(pDateStr); + } catch (ParseException pException) { + BaseLogger.log(BaseLogLevel.ERROR, "Error parsing date string: " + pDateStr + " with format: " + pFormat, pException, true); + throw pException; + } + } + + /** + * A {@link String} that converts a {@link Date} object to a string in a specified format. + * + * @param pDate {@link Date} - The date to be converted. + * @param pFormat {@link String} - The desired date format (e.g., "yyyy-MM-dd"). + * @return The date as a string in the specified format. + * @author MeAlam + * @since 1.0.0 + */ + public static String dateToString(Date pDate, String pFormat) { + try { + SimpleDateFormat formatter = new SimpleDateFormat(pFormat); + return formatter.format(pDate); + } catch (Exception pException) { + BaseLogger.log(BaseLogLevel.ERROR, "Error formatting date: " + pDate.toString() + " with format: " + pFormat, pException, true); + return pException.getMessage(); + } + } +} diff --git a/common/src/main/java/software/bluelib/utils/logging/BaseLogLevel.java b/common/src/main/java/software/bluelib/utils/logging/BaseLogLevel.java new file mode 100644 index 00000000..d7e17285 --- /dev/null +++ b/common/src/main/java/software/bluelib/utils/logging/BaseLogLevel.java @@ -0,0 +1,74 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.utils.logging; + +import java.util.logging.Level; + +/** + * A {@code class} defining custom log levels for the BlueLib logging system. + *

+ * This class extends the standard {@link Level} class to introduce additional log levels + * with specific names and integer values: + *

    + *
  • {@link #INFO} - Standard informational log level.
  • + *
  • {@link #ERROR} - Log level for error messages.
  • + *
  • {@link #WARNING} - Log level for warning messages.
  • + *
  • {@link #SUCCESS} - Custom log level for indicating successful operations.
  • + *
  • {@link #BLUELIB} - Custom log level specific to BlueLib.
  • + *
+ * + * @author MeAlam + * @since 1.0.0 + */ +public class BaseLogLevel { + + /** + * Private constructor to prevent instantiation. + *

+ * This constructor is intentionally empty to prevent creating instances of this class. + *

+ * + * @author MeAlam + * @since 1.0.0 + */ + private BaseLogLevel() { + } + + /** + * Standard informational log level. + * + * @since 1.0.0 + */ + public static final Level INFO = new Level("INFO", Level.INFO.intValue()) { + }; + /** + * Log level for error messages. + * + * @since 1.0.0 + */ + public static final Level ERROR = new Level("ERROR", Level.SEVERE.intValue()) { + }; + /** + * Log level for warning messages. + * + * @since 1.0.0 + */ + public static final Level WARNING = new Level("WARNING", Level.WARNING.intValue()) { + }; + + /** + * Custom log level for indicating successful operations. + * + * @since 1.0.0 + */ + public static final Level SUCCESS = new Level("SUCCESS", Level.INFO.intValue() + 50) { + }; + + /** + * Custom log level specific to BlueLib. + * + * @since 1.0.0 + */ + public static final Level BLUELIB = new Level("BlueLib Developer", Level.INFO.intValue() + 50) { + }; +} diff --git a/common/src/main/java/software/bluelib/utils/logging/BaseLogger.java b/common/src/main/java/software/bluelib/utils/logging/BaseLogger.java new file mode 100644 index 00000000..f0424fed --- /dev/null +++ b/common/src/main/java/software/bluelib/utils/logging/BaseLogger.java @@ -0,0 +1,167 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.utils.logging; + +import software.bluelib.BlueLibConstants; + +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A {@code public class} responsible for logging messages + * with various logging levels and configurations for {@code BlueLib}. + *

+ * Key Methods: + *

    + *
  • {@link #setBlueLibLoggingEnabled(boolean)} - Enables or disables {@code BlueLib} specific logging.
  • + *
  • {@link #isBlueLibLoggingEnabled()} - Checks if {@code BlueLib} logging is enabled.
  • + *
  • {@link #log(Level, String, Throwable, boolean)} - Logs a message with an associated {@link Throwable}.
  • + *
  • {@link #log(Level, String, boolean)} - Logs a message with a specified logging level.
  • + *
  • {@link #log(Level, String, Throwable)} - Logs a message with an associated {@link Throwable}, if logging is enabled.
  • + *
  • {@link #log(Level, String)} - Logs a message with a specified logging level, if logging is enabled.
  • + *
  • {@link #logBlueLib(String)} - Logs a {@code BlueLib} specific message.
  • + *
+ * + * @author MeAlam + * @since 1.0.0 + */ +public class BaseLogger { + + /** + * Private constructor to prevent instantiation. + *

+ * This constructor is intentionally empty to prevent creating instances of this class. + *

+ * + * @author MeAlam + * @since 1.0.0 + */ + private BaseLogger() { + } + + /** + * A {@code void} to enable or disable {@code BlueLib} specific logging. + * + * @param pEnabled {@link boolean} - Indicates whether to enable or disable BlueLib logging. + * @since 1.0.0 + */ + public static void setBlueLibLoggingEnabled(boolean pEnabled) { + BlueLibConstants.isBlueLibLoggingEnabled = pEnabled; + } + + /** + * A {@link Boolean} method that checks if BlueLib logging is enabled. + * + * @return {@code true} if BlueLib logging is enabled, {@code false} otherwise. + * @since 1.0.0 + */ + public static boolean isBlueLibLoggingEnabled() { + return BlueLibConstants.isBlueLibLoggingEnabled; + } + + /** + * A {@link Boolean} method that checks if logging is enabled. + * + * @return {@code true} if general logging is enabled, {@code false} otherwise. + * @since 1.0.0 + */ + public static boolean isLoggingEnabled() { + return BlueLibConstants.isLoggingEnabled; + } + + /** + * A {@code void} to enable or disable general logging. + * + * @param pEnabled {@link boolean} - Indicates whether to enable or disable general logging. + * @since 1.0.0 + */ + public static void setLoggingEnabled(boolean pEnabled) { + BlueLibConstants.isLoggingEnabled = pEnabled; + } + + static { + LoggerConfig.configureLogger(BlueLibConstants.LOGGER, new DefaultLogColorProvider()); + } + + /** + * A {@code public static void} that logs a message with an associated {@link Throwable} + * if {@code BlueLib} logging is enabled. + * + * @param pLogLevel {@link Level} - The logging level to use. + * @param pMessage {@link String} - The message to log. + * @param pThrowable {@link Throwable} - The throwable to log with the message. + * @param pIsBlueLib {@link boolean} - Indicates if the message is {@code BlueLib} specific. + * @since 1.0.0 + */ + public static void log(Level pLogLevel, String pMessage, Throwable pThrowable, boolean pIsBlueLib) { + if (pLogLevel == BaseLogLevel.ERROR || + pLogLevel == BaseLogLevel.WARNING || + pLogLevel == BaseLogLevel.BLUELIB || + pIsBlueLib && BlueLibConstants.isBlueLibLoggingEnabled || + !pIsBlueLib && BlueLibConstants.isLoggingEnabled) { + BlueLibConstants.LOGGER.log(pLogLevel, pMessage, pThrowable); + } + } + + /** + * A {@code public static void} that logs a message if {@code BlueLib} logging is enabled. + * + * @param pLogLevel {@link Level} - The logging level to use. + * @param pMessage {@link String} - The message to log. + * @param pIsBlueLib {@link boolean} - Indicates if the message is {@code BlueLib} specific. + * @since 1.0.0 + */ + public static void log(Level pLogLevel, String pMessage, boolean pIsBlueLib) { + if (pLogLevel == BaseLogLevel.ERROR || + pLogLevel == BaseLogLevel.WARNING || + pLogLevel == BaseLogLevel.BLUELIB || + pIsBlueLib && BlueLibConstants.isBlueLibLoggingEnabled || + !pIsBlueLib && BlueLibConstants.isLoggingEnabled) { + BlueLibConstants.LOGGER.log(pLogLevel, pMessage); + } + } + + /** + * A {@code public static void} that logs a message with an associated {@link Throwable} + * if general logging is enabled. + * + * @param pLogLevel {@link Level} - The logging level to use. + * @param pMessage {@link String} - The message to log. + * @param pThrowable {@link Throwable} - The throwable to log with the message. + * @since 1.0.0 + */ + public static void log(Level pLogLevel, String pMessage, Throwable pThrowable) { + if (pLogLevel == BaseLogLevel.ERROR || + pLogLevel == BaseLogLevel.WARNING || + pLogLevel == BaseLogLevel.BLUELIB || + BlueLibConstants.isLoggingEnabled) { + BlueLibConstants.LOGGER.log(pLogLevel, pMessage, pThrowable); + } + } + + /** + * A {@code public static void} that logs a message if general logging is enabled. + * + * @param pLogLevel {@link Level} - The logging level to use. + * @param pMessage {@link String} - The message to log. + * @since 1.0.0 + */ + public static void log(Level pLogLevel, String pMessage) { + if (pLogLevel == BaseLogLevel.ERROR || + pLogLevel == BaseLogLevel.WARNING || + pLogLevel == BaseLogLevel.BLUELIB || + BlueLibConstants.isLoggingEnabled) { + BlueLibConstants.LOGGER.log(pLogLevel, pMessage); + } + } + + /** + * A {@code public static void} that logs a {@code BlueLib} specific message at the {@code BlueLib} log level. + * + * @param pMessage {@link String} - The {@code BlueLib} message to log. + * @since 1.0.0 + */ + public static void logBlueLib(String pMessage) { + BlueLibConstants.LOGGER.log(BaseLogLevel.BLUELIB, pMessage); + } +} diff --git a/common/src/main/java/software/bluelib/utils/logging/DefaultLogColorProvider.java b/common/src/main/java/software/bluelib/utils/logging/DefaultLogColorProvider.java new file mode 100644 index 00000000..ede066e1 --- /dev/null +++ b/common/src/main/java/software/bluelib/utils/logging/DefaultLogColorProvider.java @@ -0,0 +1,52 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.utils.logging; + +import software.bluelib.interfaces.logging.ILogColorProvider; + +import java.util.logging.Level; + +/** + * A {@code class} that implements the {@link ILogColorProvider} interface + * to provide color codes for different log levels. + *

+ * This implementation uses predefined colors for various log levels, including: + *

    + *
  • {@link BaseLogLevel#ERROR} - Red color.
  • + *
  • {@link BaseLogLevel#WARNING} - Orange color.
  • + *
  • {@link BaseLogLevel#INFO} - Blue color.
  • + *
  • {@link BaseLogLevel#SUCCESS} - Green color.
  • + *
  • {@link BaseLogLevel#BLUELIB} - Green color.
  • + *
+ * + * @author MeAlam + * @since 1.0.0 + */ +public class DefaultLogColorProvider implements ILogColorProvider { + + /** + * A {@link String} that provides the color code for a given {@link Level}. + * The color code is determined based on the log level. + * + * @param pLevel {@link Level} - The log level for which to determine the color. + * @return The color code associated with the {@code pLevel}. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public String getColor(Level pLevel) { + if (pLevel == BaseLogLevel.ERROR) { + return LoggerConfig.RED; + } else if (pLevel == BaseLogLevel.WARNING) { + return LoggerConfig.ORANGE; + } else if (pLevel == BaseLogLevel.INFO) { + return LoggerConfig.BLUE; + } else if (pLevel == BaseLogLevel.SUCCESS) { + return LoggerConfig.GREEN; + } else if (pLevel == BaseLogLevel.BLUELIB) { + return LoggerConfig.GREEN; + } else { + return LoggerConfig.RESET; + } + } +} diff --git a/common/src/main/java/software/bluelib/utils/logging/LoggerConfig.java b/common/src/main/java/software/bluelib/utils/logging/LoggerConfig.java new file mode 100644 index 00000000..e4b58c03 --- /dev/null +++ b/common/src/main/java/software/bluelib/utils/logging/LoggerConfig.java @@ -0,0 +1,106 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.utils.logging; + +import software.bluelib.interfaces.logging.ILogColorProvider; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.logging.ConsoleHandler; +import java.util.logging.LogRecord; +import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; + +/** + * A {@code public abstract class} responsible for configuring logging settings, + * including setting up custom colors for log levels. + *

+ * Key Methods: + *

    + *
  • {@link #configureLogger(Logger, ILogColorProvider)} - Configures a {@link Logger} + * to use custom colors for log levels.
  • + *
+ * + * @author MeAlam + * @since 1.0.0 + */ +public abstract class LoggerConfig { + + /** + * ANSI color codes for console output. + * + * @since 1.0.0 + */ + protected static final String RESET = "\u001B[0m"; + + /** + * ANSI color codes for console output. + * + * @since 1.0.0 + */ + protected static final String RED = "\u001B[31m"; + + /** + * ANSI color codes for console output. + * + * @since 1.0.0 + */ + protected static final String ORANGE = "\u001B[38;5;214m"; + + /** + * ANSI color codes for console output. + * + * @since 1.0.0 + */ + protected static final String BLUE = "\u001B[34m"; + + /** + * ANSI color codes for console output. + * + * @since 1.0.0 + */ + protected static final String GREEN = "\u001B[38;5;10m"; + + /** + * A {@link Logger} configuration method that sets up a {@link ConsoleHandler} + * with custom color formatting based on log level using the provided {@link ILogColorProvider}. + * + * @param pLogger {@link Logger} - The logger instance to be configured. + * @param pColorProvider {@link ILogColorProvider} - Provides color codes for different log levels. + * @author MeAlam + * @since 1.0.0 + */ + public static void configureLogger(Logger pLogger, ILogColorProvider pColorProvider) { + ConsoleHandler handler = new ConsoleHandler(); + handler.setFormatter(new SimpleFormatter() { + @Override + public synchronized String format(LogRecord pRecord) { + String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")); + StringBuilder coloredMessage = new StringBuilder(pColorProvider.getColor(pRecord.getLevel()) + + "[" + timestamp + "]" + " [" + pRecord.getLevel() + "]: " + pRecord.getMessage()); + + if (pRecord.getThrown() != null) { + coloredMessage.append("\nException: ").append(pRecord.getThrown().getMessage()); + for (StackTraceElement element : pRecord.getThrown().getStackTrace()) { + String packageName = element.getClassName().substring(0, element.getClassName().lastIndexOf('.')); + String className = element.getClassName().substring(element.getClassName().lastIndexOf('.') + 1); + String methodName = element.getMethodName(); + int lineNumber = element.getLineNumber(); + + coloredMessage.append("\n\tat ") + .append(packageName).append(".") + .append(className).append(".") + .append(methodName).append("(Line: ") + .append(lineNumber).append(")"); + } + } + + coloredMessage.append(RESET); + return coloredMessage + "\n"; + } + }); + + pLogger.setUseParentHandlers(false); + pLogger.addHandler(handler); + } +} diff --git a/common/src/main/java/software/bluelib/utils/math/AlgebraicUtils.java b/common/src/main/java/software/bluelib/utils/math/AlgebraicUtils.java new file mode 100644 index 00000000..82c38c63 --- /dev/null +++ b/common/src/main/java/software/bluelib/utils/math/AlgebraicUtils.java @@ -0,0 +1,137 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.utils.math; + +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * A {@code class} providing methods for various algebraic and combinatorial calculations. + *

+ * Key Methods: + *

    + *
  • {@link #solveQuadraticEquation(double, double, double)} - Solves a quadratic equation.
  • + *
  • {@link #factorial(int)} - Calculates the factorial of a non-negative integer.
  • + *
  • {@link #calculateGCD(int, int)} - Calculates the greatest common divisor of two integers.
  • + *
  • {@link #generatePowerSet(Set)} - Generates the power set of a given set.
  • + *
+ * + * @author MeAlam + * @since 1.0.0 + */ +public class AlgebraicUtils { + + /** + * Private constructor to prevent instantiation. + *

+ * This constructor is intentionally empty to prevent creating instances of this utility class. + *

+ * + * @author MeAlam + * @since 1.0.0 + */ + private AlgebraicUtils() { + } + + /** + * A {@link Double}{@code []} that solves the quadratic equation {@code ax^2 + bx + c = 0} + * using the quadratic formula. + * + * @param pA {@link Double} - The coefficient a of the quadratic equation. + * @param pB {@link Double} - The coefficient b of the quadratic equation. + * @param pC {@link Double} - The coefficient c of the quadratic equation. + * @return An array of roots of the quadratic equation. The array may be empty if there are no real roots. + * @author MeAlam + * @since 1.0.0 + */ + public static double[] solveQuadraticEquation(double pA, double pB, double pC) { + + double discriminant = pB * pB - 4 * pA * pC; + if (discriminant < 0) { + BaseLogger.log(BaseLogLevel.WARNING, "No real roots found for the quadratic equation.", true); + return new double[0]; + } + + double sqrtDiscriminant = Math.sqrt(discriminant); + double root1 = (-pB + sqrtDiscriminant) / (2 * pA); + double root2 = (-pB - sqrtDiscriminant) / (2 * pA); + + BaseLogger.log(BaseLogLevel.INFO, "Roots found: root1=" + root1 + ", root2=" + root2, true); + return new double[]{root1, root2}; + } + + /** + * A {@link Long} that calculates the factorial of a non-negative integer. + * + * @param pNumber {@link Integer} - The non-negative integer. + * @return The factorial of the integer. + * @throws IllegalArgumentException if {@code pNumber} is negative. + * @author MeAlam + * @since 1.0.0 + */ + public static long factorial(int pNumber) { + if (pNumber < 0) { + IllegalArgumentException exception = new IllegalArgumentException("Number must be non-negative."); + BaseLogger.log(BaseLogLevel.ERROR, "Attempted to calculate factorial of a negative number: " + pNumber, exception, true); + throw exception; + } + + long result = 1; + for (int i = 1; i <= pNumber; i++) { + result *= i; + } + + return result; + } + + /** + * A {@link Integer} that calculates the greatest common divisor of two integers using the Euclidean algorithm. + * + * @param pA {@link Integer} - The first integer. + * @param pB {@link Integer} - The second integer. + * @return The greatest common divisor of {@code pA} and {@code pB}. + * @author MeAlam + * @since 1.0.0 + */ + public static int calculateGCD(int pA, int pB) { + + while (pB != 0) { + int temp = pB; + pB = pA % pB; + pA = temp; + } + + return pA; + } + + /** + * A {@link List} that generates the power set (all subsets) of a given set. + * + * @param pSet {@link Set} - The input set. + * @param The type of elements in the set. + * @return A list of all subsets of the input set. + * @author MeAlam + * @since 1.0.0 + */ + public static List> generatePowerSet(Set pSet) { + + List> powerSet = new ArrayList<>(); + powerSet.add(new HashSet<>()); + for (T element : pSet) { + List> newSubsets = new ArrayList<>(); + for (Set subset : powerSet) { + Set newSubset = new HashSet<>(subset); + newSubset.add(element); + newSubsets.add(newSubset); + } + powerSet.addAll(newSubsets); + } + + return powerSet; + } +} diff --git a/common/src/main/java/software/bluelib/utils/math/GeometricUtils.java b/common/src/main/java/software/bluelib/utils/math/GeometricUtils.java new file mode 100644 index 00000000..20116480 --- /dev/null +++ b/common/src/main/java/software/bluelib/utils/math/GeometricUtils.java @@ -0,0 +1,255 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.utils.math; + +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +/** + * A {@code class} providing utility methods for various geometric calculations. + *

+ * Key Methods: + *

    + *
  • {@link #calculateDistance2D(double, double, double, double)} - Calculates the Euclidean distance between two points in 2D space.
  • + *
  • {@link #calculateDistance3D(double, double, double, double, double, double)} - Calculates the Euclidean distance between two points in 3D space.
  • + *
  • {@link #calculateCircleArea(double)} - Calculates the area of a circle given its radius.
  • + *
  • {@link #calculateCircleCircumference(double)} - Calculates the circumference of a circle given its radius.
  • + *
  • {@link #calculateRectangleArea(double, double)} - Calculates the area of a rectangle given its width and height.
  • + *
  • {@link #calculateRectanglePerimeter(double, double)} - Calculates the perimeter of a rectangle given its width and height.
  • + *
  • {@link #calculateTriangleArea(double, double)} - Calculates the area of a triangle given its base and height.
  • + *
  • {@link #calculateTrianglePerimeter(double, double, double)} - Calculates the perimeter of a triangle given its three sides.
  • + *
  • {@link #calculateSphereVolume(double)} - Calculates the volume of a sphere given its radius.
  • + *
  • {@link #calculateCubeSurfaceArea(double)} - Calculates the surface area of a cube given its side length.
  • + *
  • {@link #calculateCylinderVolume(double, double)} - Calculates the volume of a cylinder given its radius and height.
  • + *
  • {@link #calculateConeSurfaceArea(double, double)} - Calculates the surface area of a cone given its radius and slant height.
  • + *
+ * + * @author MeAlam + * @since 1.0.0 + */ +public class GeometricUtils { + + /** + * Private constructor to prevent instantiation. + *

+ * This constructor is intentionally empty to prevent creating instances of this utility class. + *

+ * + * @author MeAlam + * @since 1.0.0 + */ + private GeometricUtils() { + } + + /** + * A {@link Double} that calculates the Euclidean distance between two points in 2D space. + * + * @param pX1 {@link double} - The x-coordinate of the first point. + * @param pY1 {@link double} - The y-coordinate of the first point. + * @param pX2 {@link double} - The x-coordinate of the second point. + * @param pY2 {@link double} - The y-coordinate of the second point. + * @return The distance between the two points. + * @author MeAlam + * @since 1.0.0 + */ + public static double calculateDistance2D(double pX1, double pY1, double pX2, double pY2) { + double dx = pX2 - pX1; + double dy = pY2 - pY1; + return Math.sqrt(dx * dx + dy * dy); + } + + /** + * A {@link Double} that calculates the Euclidean distance between two points in 3D space. + * + * @param pX1 {@link Double} - The x-coordinate of the first point. + * @param pY1 {@link Double} - The y-coordinate of the first point. + * @param pZ1 {@link Double} - The z-coordinate of the first point. + * @param pX2 {@link Double} - The x-coordinate of the second point. + * @param pY2 {@link Double} - The y-coordinate of the second point. + * @param pZ2 {@link Double} - The z-coordinate of the second point. + * @return The distance between the two points in 3D space. + * @author MeAlam + * @since 1.0.0 + */ + public static double calculateDistance3D(double pX1, double pY1, double pZ1, double pX2, double pY2, double pZ2) { + double dx = pX2 - pX1; + double dy = pY2 - pY1; + double dz = pZ2 - pZ1; + return Math.sqrt(dx * dx + dy * dy + dz * dz); + } + + /** + * A {@link Double} that calculates the area of a circle given its radius. + * + * @param pRadius {@link Double} - The radius of the circle. + * @return The area of the circle. + * @author MeAlam + * @since 1.0.0 + */ + public static double calculateCircleArea(double pRadius) { + if (pRadius < 0) { + Throwable throwable = new IllegalArgumentException("Radius must be non-negative."); + BaseLogger.log(BaseLogLevel.ERROR, "Error calculating circle area", throwable, true); + return Double.NaN; + } + return Math.PI * pRadius * pRadius; + } + + /** + * A {@link Double} that calculates the circumference of a circle given its radius. + * + * @param pRadius {@link Double} - The radius of the circle. + * @return The circumference of the circle. + * @author MeAlam + * @since 1.0.0 + */ + public static double calculateCircleCircumference(double pRadius) { + if (pRadius < 0) { + Throwable throwable = new IllegalArgumentException("Radius must be non-negative."); + BaseLogger.log(BaseLogLevel.ERROR, "Error calculating circle circumference", throwable, true); + return Double.NaN; + } + return 2 * Math.PI * pRadius; + } + + /** + * A {@link Double} that calculates the area of a rectangle given its width and height. + * + * @param pWidth {@link Double} - The width of the rectangle. + * @param pHeight {@link Double} - The height of the rectangle. + * @return The area of the rectangle. + * @author MeAlam + * @since 1.0.0 + */ + public static double calculateRectangleArea(double pWidth, double pHeight) { + if (pWidth < 0 || pHeight < 0) { + Throwable throwable = new IllegalArgumentException("Width and height must be non-negative."); + BaseLogger.log(BaseLogLevel.ERROR, "Error calculating rectangle area", throwable, true); + return Double.NaN; + } + return pWidth * pHeight; + } + + /** + * A {@link Double} that calculates the perimeter of a rectangle given its width and height. + * + * @param pWidth {@link Double} - The width of the rectangle. + * @param pHeight {@link Double} - The height of the rectangle. + * @return The perimeter of the rectangle. + * @author MeAlam + * @since 1.0.0 + */ + public static double calculateRectanglePerimeter(double pWidth, double pHeight) { + if (pWidth < 0 || pHeight < 0) { + Throwable throwable = new IllegalArgumentException("Width and height must be non-negative."); + BaseLogger.log(BaseLogLevel.ERROR, "Error calculating rectangle perimeter", throwable, true); + return Double.NaN; + } + return 2 * (pWidth + pHeight); + } + + /** + * A {@link Double} that calculates the area of a triangle given its base and height. + * + * @param pBase {@link Double} - The base of the triangle. + * @param pHeight {@link Double} - The height of the triangle. + * @return The area of the triangle. + * @since 1.0.0 + */ + public static double calculateTriangleArea(double pBase, double pHeight) { + if (pBase < 0 || pHeight < 0) { + Throwable throwable = new IllegalArgumentException("Base and height must be non-negative."); + BaseLogger.log(BaseLogLevel.ERROR, "Error calculating triangle area", throwable, true); + return Double.NaN; + } + return 0.5 * pBase * pHeight; + } + + /** + * A {@link Double} that calculates the perimeter of a triangle given its three sides. + * + * @param pSide1 {@link Double} - The first side of the triangle. + * @param pSide2 {@link Double} - The second side of the triangle. + * @param pSide3 {@link Double} - The third side of the triangle. + * @return The perimeter of the triangle. + * @since 1.0.0 + */ + public static double calculateTrianglePerimeter(double pSide1, double pSide2, double pSide3) { + if (pSide1 < 0 || pSide2 < 0 || pSide3 < 0) { + Throwable throwable = new IllegalArgumentException("Sides must be non-negative."); + BaseLogger.log(BaseLogLevel.ERROR, "Error calculating triangle perimeter", throwable, true); + return Double.NaN; + } + return pSide1 + pSide2 + pSide3; + } + + /** + * A {@link Double} that calculates the volume of a sphere given its radius. + * + * @param pRadius {@link Double} - The radius of the sphere. + * @return The volume of the sphere. + * @author MeAlam + * @since 1.0.0 + */ + public static double calculateSphereVolume(double pRadius) { + if (pRadius < 0) { + Throwable throwable = new IllegalArgumentException("Radius must be non-negative."); + BaseLogger.log(BaseLogLevel.ERROR, "Error calculating sphere volume", throwable, true); + return Double.NaN; + } + return (4.0 / 3.0) * Math.PI * Math.pow(pRadius, 3); + } + + /** + * A {@link Double} that calculates the surface area of a cube given its side length. + * + * @param pSideLength {@link Double} - The length of a side of the cube. + * @return The surface area of the cube. + * @author MeAlam + * @since 1.0.0 + */ + public static double calculateCubeSurfaceArea(double pSideLength) { + if (pSideLength < 0) { + Throwable throwable = new IllegalArgumentException("Side length must be non-negative."); + BaseLogger.log(BaseLogLevel.ERROR, "Error calculating cube surface area", throwable, true); + return Double.NaN; + } + return 6 * Math.pow(pSideLength, 2); + } + + /** + * A {@link Double} that calculates the volume of a cylinder given its radius and height. + * + * @param pRadius {@link Double} - The radius of the cylinder's base. + * @param pHeight {@link Double} - The height of the cylinder. + * @return The volume of the cylinder. + * @author MeAlam + * @since 1.0.0 + */ + public static double calculateCylinderVolume(double pRadius, double pHeight) { + if (pRadius < 0 || pHeight < 0) { + Throwable throwable = new IllegalArgumentException("Radius and height must be non-negative."); + BaseLogger.log(BaseLogLevel.ERROR, "Error calculating cylinder volume", throwable, true); + return Double.NaN; + } + return Math.PI * Math.pow(pRadius, 2) * pHeight; + } + + /** + * A {@link Double} that calculates the surface area of a cone given its radius and slant height. + * + * @param pRadius {@link Double} - The radius of the base of the cone. + * @param pSlantHeight {@link Double} - The slant height of the cone. + * @return The surface area of the cone. + * @author MeAlam + * @since 1.0.0 + */ + public static double calculateConeSurfaceArea(double pRadius, double pSlantHeight) { + if (pRadius < 0 || pSlantHeight < 0) { + Throwable throwable = new IllegalArgumentException("Radius and slant height must be non-negative."); + BaseLogger.log(BaseLogLevel.ERROR, "Error calculating cone surface area", throwable, true); + return Double.NaN; + } + return Math.PI * pRadius * (pRadius + pSlantHeight); + } +} diff --git a/common/src/main/java/software/bluelib/utils/math/MatrixUtils.java b/common/src/main/java/software/bluelib/utils/math/MatrixUtils.java new file mode 100644 index 00000000..38d39d65 --- /dev/null +++ b/common/src/main/java/software/bluelib/utils/math/MatrixUtils.java @@ -0,0 +1,132 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.utils.math; + +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +/** + * A {@code class} providing utility methods for matrix operations. + *

+ * Key Methods: + *

    + *
  • {@link #multiplyMatrices(double[][], double[][])} - Performs matrix multiplication on two matrices.
  • + *
  • {@link #transposeMatrix(double[][])} - Computes the transpose of a matrix.
  • + *
  • {@link #calculate2x2MatrixDeterminant(double[][])} - Calculates the determinant of a 2x2 matrix.
  • + *
  • {@link #invert2x2Matrix(double[][])} - Calculates the inverse of a 2x2 matrix.
  • + *
+ * + * @author MeAlam + * @since 1.0.0 + */ +public class MatrixUtils { + + /** + * Private constructor to prevent instantiation. + *

+ * This constructor is intentionally empty to prevent creating instances of this utility class. + *

+ * + * @author MeAlam + * @since 1.0.0 + */ + private MatrixUtils() { + } + + /** + * A {@link Double}{@code [][]} that performs matrix multiplication on two matrices. + * + * @param pMatrixA {@link Double}{@code [][]} - The first matrix to be multiplied. + * @param pMatrixB {@link Double}{@code [][]} - The second matrix to be multiplied. + * @return The product of the two matrices. + * @throws IllegalArgumentException if the number of columns in the first matrix does not match the number of rows in the second matrix. + * @author MeAlam + * @since 1.0.0 + */ + public static double[][] multiplyMatrices(double[][] pMatrixA, double[][] pMatrixB) { + int rowsA = pMatrixA.length; + int colsA = pMatrixA[0].length; + int colsB = pMatrixB[0].length; + if (colsA != pMatrixB.length) { + Throwable throwable = new IllegalArgumentException("Number of columns in the first matrix must be equal to the number of rows in the second matrix."); + BaseLogger.log(BaseLogLevel.ERROR, "Error performing matrix multiplication", throwable, true); + return new double[0][0]; + } + double[][] result = new double[rowsA][colsB]; + for (int i = 0; i < rowsA; i++) { + for (int j = 0; j < colsB; j++) { + for (int k = 0; k < colsA; k++) { + result[i][j] += pMatrixA[i][k] * pMatrixB[k][j]; + } + } + } + return result; + } + + /** + * A {@link Double}{@code [][]} that computes the transpose of a matrix. + * + * @param pMatrix {@link Double}{@code [][]} - The matrix to be transposed. + * @return The transposed matrix. + * @author MeAlam + * @since 1.0.0 + */ + public static double[][] transposeMatrix(double[][] pMatrix) { + int rows = pMatrix.length; + int cols = pMatrix[0].length; + double[][] transposed = new double[cols][rows]; + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cols; j++) { + transposed[j][i] = pMatrix[i][j]; + } + } + return transposed; + } + + /** + * A {@link Double}{@code [][]} that calculates the determinant of a 2x2 matrix. + * + * @param pMatrix {@link Double}{@code [][]} - The 2x2 matrix. + * @return The determinant of the matrix. + * @throws IllegalArgumentException if the matrix is not 2x2. + * @author MeAlam + * @since 1.0.0 + */ + public static double calculate2x2MatrixDeterminant(double[][] pMatrix) { + if (pMatrix.length != 2 || pMatrix[0].length != 2) { + Throwable throwable = new IllegalArgumentException("Matrix must be 2x2."); + BaseLogger.log(BaseLogLevel.ERROR, "Error calculating 2x2 matrix determinant", throwable, true); + return Double.NaN; + } + return pMatrix[0][0] * pMatrix[1][1] - pMatrix[0][1] * pMatrix[1][0]; + } + + /** + * A {@link Double}{@code [][]} that calculates the inverse of a 2x2 matrix. + * + * @param pMatrix {@link Double}{@code [][]} - The 2x2 matrix. + * @return The inverse of the matrix. + * @throws IllegalArgumentException if the matrix is not invertible or not 2x2. + * @author MeAlam + * @since 1.0.0 + */ + public static double[][] invert2x2Matrix(double[][] pMatrix) { + if (pMatrix.length != 2 || pMatrix[0].length != 2) { + Throwable throwable = new IllegalArgumentException("Matrix must be 2x2."); + BaseLogger.log(BaseLogLevel.ERROR, "Error inverting 2x2 matrix", throwable, true); + return new double[0][0]; + } + double determinant = calculate2x2MatrixDeterminant(pMatrix); + if (determinant == 0) { + Throwable throwable = new IllegalArgumentException("Matrix is not invertible."); + BaseLogger.log(BaseLogLevel.ERROR, "Error inverting 2x2 matrix", throwable, true); + return new double[0][0]; + } + double[][] inverse = new double[2][2]; + inverse[0][0] = pMatrix[1][1] / determinant; + inverse[0][1] = -pMatrix[0][1] / determinant; + inverse[1][0] = -pMatrix[1][0] / determinant; + inverse[1][1] = pMatrix[0][0] / determinant; + return inverse; + } +} diff --git a/common/src/main/java/software/bluelib/utils/math/MiscUtils.java b/common/src/main/java/software/bluelib/utils/math/MiscUtils.java new file mode 100644 index 00000000..35729c20 --- /dev/null +++ b/common/src/main/java/software/bluelib/utils/math/MiscUtils.java @@ -0,0 +1,129 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.utils.math; + +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +/** + * A {@code class} providing utility methods for various common operations. + *

+ * Key Methods: + *

    + *
  • {@link #isValidEmail(String)} - Checks if a string is a valid email address.
  • + *
  • {@link #stringToIntWithDefault(String, int)} - Converts a string to an integer with a default value if conversion fails.
  • + *
  • {@link #calculateLevenshteinDistance(String, String)} - Calculates the Levenshtein distance between two strings.
  • + *
  • {@link #hexToRGB(String)} - Converts a hexadecimal color code to an RGB array.
  • + *
+ * + * @author MeAlam + * @since 1.0.0 + */ +public class MiscUtils { + + /** + * Private constructor to prevent instantiation. + *

+ * This constructor is intentionally empty to prevent creating instances of this utility class. + *

+ * + * @author MeAlam + * @since 1.0.0 + */ + private MiscUtils() { + } + + /** + * A {@link Boolean} that checks if a string is a valid email address. + * + * @param pEmail {@link String} - The string to be checked. + * @return {@code true} if the string is a valid email address, {@code false} otherwise. + * @author MeAlam + * @since 1.0.0 + */ + public static boolean isValidEmail(String pEmail) { + String emailRegex = "^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$"; + return pEmail != null && pEmail.matches(emailRegex); + } + + /** + * A {@link Integer} that converts a string to an integer, returning a default value if the string is not a valid integer. + * + * @param pString {@link String} - The string to be converted. + * @param pDefaultValue {@link Integer} - The default value to return if the string is not a valid integer. + * @return The integer value of the string, or {@code pDefaultValue} if the string is not a valid integer. + * @author MeAlam + * @since 1.0.0 + */ + public static int stringToIntWithDefault(String pString, int pDefaultValue) { + try { + return Integer.parseInt(pString); + } catch (NumberFormatException pException) { + BaseLogger.log(BaseLogLevel.ERROR, "Error converting string to integer", pException, true); + return pDefaultValue; + } + } + + /** + * A {@link Integer} that calculates the Levenshtein distance between two strings. + * + * @param pStr1 {@link String} - The first string. + * @param pStr2 {@link String} - The second string. + * @return The Levenshtein distance between the two strings. + * @author MeAlam + * @since 1.0.0 + */ + public static int calculateLevenshteinDistance(String pStr1, String pStr2) { + int[][] dp = new int[pStr1.length() + 1][pStr2.length() + 1]; + for (int i = 0; i <= pStr1.length(); i++) { + for (int j = 0; j <= pStr2.length(); j++) { + if (i == 0) { + dp[i][j] = j; + } else if (j == 0) { + dp[i][j] = i; + } else { + int cost = (pStr1.charAt(i - 1) == pStr2.charAt(j - 1)) ? 0 : 1; + dp[i][j] = Math.min( + Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1), + dp[i - 1][j - 1] + cost + ); + } + } + } + return dp[pStr1.length()][pStr2.length()]; + } + + /** + * A {@link Integer}{@code []} that converts a hexadecimal color code to an RGB array. + * + * @param pHex {@link String} - The hexadecimal color code (e.g., "#FFFFFF"). + * @return An array containing the RGB values. + * @throws IllegalArgumentException if the hexadecimal color code is invalid. + * @author MeAlam + * @since 1.0.0 + */ + public static int[] hexToRGB(String pHex) { + if (pHex == null || pHex.isEmpty()) { + Throwable throwable = new IllegalArgumentException("Hex color code cannot be null or empty."); + BaseLogger.log(BaseLogLevel.ERROR, "Error converting hex to RGB", throwable, true); + return new int[]{0, 0, 0}; + } + if (pHex.charAt(0) == '#') { + pHex = pHex.substring(1); + } + if (pHex.length() != 6) { + Throwable throwable = new IllegalArgumentException("Invalid hex color code."); + BaseLogger.log(BaseLogLevel.ERROR, "Error converting hex to RGB", throwable, true); + return new int[]{0, 0, 0}; + } + try { + int r = Integer.parseInt(pHex.substring(0, 2), 16); + int g = Integer.parseInt(pHex.substring(2, 4), 16); + int b = Integer.parseInt(pHex.substring(4, 6), 16); + return new int[]{r, g, b}; + } catch (NumberFormatException pException) { + BaseLogger.log(BaseLogLevel.ERROR, "Error parsing hex color code to RGB", pException, true); + return new int[]{0, 0, 0}; + } + } +} diff --git a/common/src/main/java/software/bluelib/utils/math/RandomGenUtils.java b/common/src/main/java/software/bluelib/utils/math/RandomGenUtils.java new file mode 100644 index 00000000..2c60f24b --- /dev/null +++ b/common/src/main/java/software/bluelib/utils/math/RandomGenUtils.java @@ -0,0 +1,135 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.utils.math; + +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +/** + * A {@code class} for generating random values of various types. + *

+ * This class provides static methods to generate random integers, doubles, booleans, and alphanumeric strings. + * The methods offer flexibility in specifying the range or length for the generated values, and they ensure + * proper logging and error handling if invalid parameters are provided. + *

+ *

+ * Key Methods: + *

    + *
  • {@link #generateRandomInt(int, int)} - Generates a random integer between a specified minimum and maximum value (inclusive).
  • + *
  • {@link #generateRandomDouble(double, double)} - Generates a random double between a specified minimum and maximum value (inclusive).
  • + *
  • {@link #generateRandomBoolean()} - Generates a random boolean value.
  • + *
  • {@link #generateRandomString(int)} - Generates a random alphanumeric string of a specified length.
  • + *
  • {@link #generateRandomStringWithPrefix(String, int)} - Generates a random alphanumeric string with a specified prefix and length.
  • + *
+ *

+ * Each method logs errors using {@link BaseLogger} when invalid parameters are provided (e.g., negative lengths + * or minimum values greater than maximum values) and returns default values (e.g., `0` for integers and `"unknown"` for strings) + * in such cases. + *

+ * + * @author MeAlam + * @since 1.0.0 + */ +public class RandomGenUtils { + + /** + * Private constructor to prevent instantiation. + *

+ * This constructor is intentionally empty to prevent creating instances of this utility class. + *

+ * + * @author MeAlam + * @since 1.0.0 + */ + private RandomGenUtils() { + } + + /** + * A {@link Integer} that generates a random integer between a specified minimum and maximum value (inclusive). + * + * @param pMin {@link Integer} - The minimum value (inclusive). + * @param pMax {@link Integer} - The maximum value (inclusive). + * @return A random integer between {@code pMin} and {@code pMax}. + * @author MeAlam + * @since 1.0.0 + */ + public static int generateRandomInt(int pMin, int pMax) { + if (pMin > pMax) { + Throwable throwable = new IllegalArgumentException("Minimum value must not be greater than maximum value."); + BaseLogger.log(BaseLogLevel.WARNING, "Error generating random integer", throwable, true); + return 0; + } + return pMin + (int) (Math.random() * (pMax - pMin + 1)); + } + + /** + * A {@link Double} that generates a random double between a specified minimum and maximum value (inclusive). + * + * @param pMin {@link Double} - The minimum value (inclusive). + * @param pMax {@link Double} - The maximum value (inclusive). + * @return A random double between {@code pMin} and {@code pMax}. + * @author MeAlam + * @since 1.0.0 + */ + public static double generateRandomDouble(double pMin, double pMax) { + if (pMin > pMax) { + Throwable throwable = new IllegalArgumentException("Minimum value must not be greater than maximum value."); + BaseLogger.log(BaseLogLevel.WARNING, "Error generating random double", true); + return 0; + } + return pMin + Math.random() * (pMax - pMin); + } + + /** + * A {@link Boolean} that generates a random boolean value. + * + * @return A random boolean value. + * @author MeAlam + * @since 1.0.0 + */ + public static boolean generateRandomBoolean() { + return Math.random() < 0.5; + } + + /** + * A {@link String} that generates a random alphanumeric string of a specified length. + * + * @param pLength {@link Integer} - The length of the string to be generated. + * @return A random alphanumeric string of the specified length. + * @throws IllegalArgumentException if {@code pLength} is negative. + * @author MeAlam + * @since 1.0.0 + */ + public static String generateRandomString(int pLength) { + if (pLength < 0) { + Throwable throwable = new IllegalArgumentException("Length must be non-negative."); + BaseLogger.log(BaseLogLevel.WARNING, "Error generating random string", throwable, true); + return "unknown"; + } + String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + StringBuilder sb = new StringBuilder(pLength); + for (int i = 0; i < pLength; i++) { + int index = (int) (Math.random() * characters.length()); + sb.append(characters.charAt(index)); + } + return sb.toString(); + } + + /** + * A {@link String} that generates a random alphanumeric string of a specified length with a specified prefix. + * + * @param pPrefix {@link String} - The prefix of the string. + * @param pLength {@link Integer} - The length of the string to be generated. + * @return A random alphanumeric string of the specified length with the specified prefix. + * @throws IllegalArgumentException if {@code pLength} is negative. + * @since 1.0.0 + */ + public static String generateRandomStringWithPrefix(String pPrefix, int pLength) { + if (pLength < 0) { + Throwable throwable = new IllegalArgumentException("Length must be non-negative."); + BaseLogger.log(BaseLogLevel.WARNING, "Error generating random string with prefix", throwable, true); + return "unknown"; + } + return pPrefix + generateRandomString(pLength - pPrefix.length()); + } +} diff --git a/common/src/main/java/software/bluelib/utils/math/StatisticalUtils.java b/common/src/main/java/software/bluelib/utils/math/StatisticalUtils.java new file mode 100644 index 00000000..dcdd1b9b --- /dev/null +++ b/common/src/main/java/software/bluelib/utils/math/StatisticalUtils.java @@ -0,0 +1,228 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.utils.math; + +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * A {@code class} for performing various statistical calculations on arrays of double values. + *

+ * This class includes methods to compute key statistical metrics such as mean, median, mode, standard deviation, + * variance, range, and coefficient of variation. Each method logs appropriate messages for success or warnings + * when the input array is empty. + *

+ *

+ * Key Methods: + *

    + *
  • {@link #calculateMean(double[])} - Calculates the mean (average) of an array of values.
  • + *
  • {@link #calculateMedian(double[])} - Calculates the median value of an array of values.
  • + *
  • {@link #calculateMode(double[])} - Determines the mode (most frequent value) of an array of values.
  • + *
  • {@link #calculateStandardDeviation(double[])} - Computes the standard deviation of an array of values.
  • + *
  • {@link #calculateVariance(double[])} - Computes the variance of an array of values.
  • + *
  • {@link #calculateRange(double[])} - Determines the range (difference between maximum and minimum) of an array of values.
  • + *
  • {@link #calculateCoefficientOfVariation(double[])} - Calculates the coefficient of variation (CV) of an array of values.
  • + *
+ *

+ * Each method logs a success message with the computed value or a warning if the input array is empty. + * The logging is done via {@link BaseLogger}, ensuring that any issues or results are recorded appropriately. + *

+ * + * @author MeAlam + * @since 1.0.0 + */ +public class StatisticalUtils { + + /** + * Private constructor to prevent instantiation. + *

+ * This constructor is intentionally empty to prevent creating instances of this utility class. + *

+ * + * @author MeAlam + * @since 1.0.0 + */ + private StatisticalUtils() { + } + + /** + * A {@link Double} that calculates the mean (average) of an array of values. + *

+ * Logs a warning if the array is empty and a success message with the calculated mean. + *

+ * + * @param pValues {@link double[]} - The array of values to calculate the mean for. + * @return The mean of the values. + * @author MeAlam + * @since 1.0.0 + */ + public static double calculateMean(double[] pValues) { + if (pValues.length == 0) { + BaseLogger.log(BaseLogLevel.WARNING, "Array is empty, mean calculation might fail.", true); + return 0; + } + + double sum = 0; + for (double value : pValues) { + sum += value; + } + return sum / pValues.length; + } + + /** + * A {@link Double}{@code []} that calculates the median of an array of values. + *

+ * Logs a warning if the array is empty and a success message with the calculated median. + *

+ * + * @param pValues {@link Double}{@code []} - The array of values to calculate the median for. + * @return The median of the values. + * @author MeAlam + * @since 1.0.0 + */ + public static double calculateMedian(double[] pValues) { + if (pValues.length == 0) { + BaseLogger.log(BaseLogLevel.WARNING, "Array is empty, median calculation might fail.", true); + return 0; + } + + double[] sorted = pValues.clone(); + Arrays.sort(sorted); + int middle = sorted.length / 2; + + return (sorted.length % 2 == 0) ? + (sorted[middle - 1] + sorted[middle]) / 2.0 : + sorted[middle]; + } + + /** + * A {@link Double}{@code []} that calculates the mode (the most frequent value) of an array of values. + *

+ * Logs a warning if the array is empty and a success message with the calculated mode. + *

+ * + * @param pValues {@link Double}{@code []} - The array of values to calculate the mode for. + * @return The mode of the values. + * @author MeAlam + * @since 1.0.0 + */ + public static double calculateMode(double[] pValues) { + if (pValues.length == 0) { + BaseLogger.log(BaseLogLevel.WARNING, "Array is empty, mode calculation might fail.", true); + return 0; + } + + Map frequencyMap = new HashMap<>(); + for (double value : pValues) { + frequencyMap.put(value, frequencyMap.getOrDefault(value, 0) + 1); + } + + double mode = pValues[0]; + int maxCount = 0; + for (Map.Entry entry : frequencyMap.entrySet()) { + if (entry.getValue() > maxCount) { + maxCount = entry.getValue(); + mode = entry.getKey(); + } + } + return mode; + } + + /** + * A {@link Double}{@code []} that calculates the standard deviation of an array of values. + *

+ * Logs a warning if the array is empty and a success message with the calculated standard deviation. + *

+ * + * @param pValues {@link Double}{@code []} - The array of values to calculate the standard deviation for. + * @return The standard deviation of the values. + * @author MeAlam + * @since 1.0.0 + */ + public static double calculateStandardDeviation(double[] pValues) { + if (pValues.length == 0) { + BaseLogger.log(BaseLogLevel.WARNING, "Array is empty, standard deviation calculation might fail.", true); + return 0; + } + + double mean = calculateMean(pValues); + double sumSquaredDifferences = 0; + for (double value : pValues) { + sumSquaredDifferences += Math.pow(value - mean, 2); + } + return Math.sqrt(sumSquaredDifferences / pValues.length); + } + + /** + * A {@link Double}{@code []} that calculates the variance of an array of values. + *

+ * Logs a warning if the array is empty and a success message with the calculated variance. + *

+ * + * @param pValues {@link Double}{@code []} - The array of values to calculate the variance for. + * @return The variance of the values. + * @author MeAlam + * @since 1.0.0 + */ + public static double calculateVariance(double[] pValues) { + if (pValues.length == 0) { + BaseLogger.log(BaseLogLevel.WARNING, "Array is empty, variance calculation might fail.", true); + return 0; + } + + double mean = calculateMean(pValues); + double sumSquaredDifferences = 0; + for (double value : pValues) { + sumSquaredDifferences += Math.pow(value - mean, 2); + } + return sumSquaredDifferences / pValues.length; + } + + /** + * A {@link Double}{@code []} that calculates the range (difference between maximum and minimum) of an array of values. + *

+ * Logs a warning if the array is empty and a success message with the calculated range. + *

+ * + * @param pValues {@link Double}{@code []} - The array of values to calculate the range for. + * @return The range of the values. + * @author MeAlam + * @since 1.0.0 + */ + public static double calculateRange(double[] pValues) { + if (pValues.length == 0) { + BaseLogger.log(BaseLogLevel.WARNING, "Array is empty, range calculation might fail.", true); + return 0; + } + + double max = Arrays.stream(pValues).max().orElseThrow(); + double min = Arrays.stream(pValues).min().orElseThrow(); + return max - min; + } + + /** + * A {@link Double}{@code []} that calculates the coefficient of variation of an array of values. + *

+ * Logs a warning if the array is empty and a success message with the calculated coefficient of variation. + *

+ * + * @param pValues {@link Double}{@code []} - The array of values to calculate the coefficient of variation for. + * @return The coefficient of variation of the values. + * @author MeAlam + * @since 1.0.0 + */ + public static double calculateCoefficientOfVariation(double[] pValues) { + if (pValues.length == 0) { + BaseLogger.log(BaseLogLevel.WARNING, "Array is empty, coefficient of variation calculation might fail.", true); + return 0; + } + + double mean = calculateMean(pValues); + double stdDev = calculateStandardDeviation(pValues); + return (stdDev / mean) * 100; + } +} diff --git a/common/src/main/resources/bluelib.png b/common/src/main/resources/bluelib.png new file mode 100644 index 0000000000000000000000000000000000000000..1896bcddc2e07570d39d3922ac91b9824e85c4b5 GIT binary patch literal 145980 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%0D$ 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 z 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 O0000 + * This class handles the initialization of BlueLib by registering a client tick event that ensures + * the mod is initialized only once during the game runtime. + *

+ *

+ * Key Methods: + *

    + *
  • {@link #onInitialize()} - Registers the client tick event to initialize BlueLib.
  • + *
+ * + * @author MeAlam + * @since 1.0.0 + */ +public class BlueLib implements ModInitializer { + + /** + * A {@code private} {@link Boolean} flag indicating whether the mod has been initialized. + *

+ * This ensures that the {@link BlueLibCommon#init()} method is called only once during the game's lifecycle. + *

+ * + * @since 1.0.0 + */ + private boolean hasInitialized = false; + + /** + * A {@code public void} that registers a client tick event to initialize the BlueLib mod. + *

+ * This method checks if the mod is being run in developer mode and if the Geckolib mod is loaded. If both conditions + * are met, it initializes the entities and registers the event listeners for the reload handler. + *

+ *

+ * This method uses {@link ClientTickEvents#END_CLIENT_TICK} to register a callback that checks + * whether the mod has already been initialized and calls {@link BlueLibCommon#init()} if necessary. + *

+ * + * @author MeAlam + * @since 1.0.0 + */ + @Override + public void onInitialize() { + if (BlueLibCommon.isDeveloperMode() && BlueLibCommon.PLATFORM.isModLoaded("geckolib") && BlueLibConstants.isExampleEnabled) { + ModEntities.initializeEntities(); + ReloadHandler.registerEventListeners(); + FabricDefaultAttributeRegistry.register(ModEntities.EXAMPLE_ONE, DragonEntity.createMobAttributes()); + FabricDefaultAttributeRegistry.register(ModEntities.EXAMPLE_TWO, RexEntity.createMobAttributes()); + } + ClientTickEvents.END_CLIENT_TICK.register(client -> { + if (!hasInitialized) { + hasInitialized = true; + BlueLibCommon.init(); + } + }); + } +} diff --git a/fabric/src/main/java/software/bluelib/entity/variant/VariantLoader.java b/fabric/src/main/java/software/bluelib/entity/variant/VariantLoader.java new file mode 100644 index 00000000..f8257571 --- /dev/null +++ b/fabric/src/main/java/software/bluelib/entity/variant/VariantLoader.java @@ -0,0 +1,189 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.entity.variant; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.packs.resources.ResourceManager; +import software.bluelib.interfaces.variant.base.IVariantEntityBase; +import software.bluelib.json.JSONLoader; +import software.bluelib.json.JSONMerger; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +import java.util.*; + +/** + * A {@code public class} that implements the {@link IVariantEntityBase} {@code interface} that manages the loading and storage of entity variants. + *

+ * The class handles loading and merging of JSON Data by utilizing the {@link JSONLoader} and {@link JSONMerger} classes.
+ * To load the Variants it loops through all resources in a folder and merges them into a single {@link JsonObject}.
+ * The merged JSON data is then parsed into {@link VariantParameter} instances and stored in {@link #entityVariantsMap}.
+ *

+ * Key Methods: + *
    + *
  • {@link #loadVariants(String, MinecraftServer, String)} - Loads and merges variant data by looping thru all resources in a folder.
  • + *
  • {@link #getVariantsFromEntity(String)} - Retrieves the list of loaded {@link VariantParameter} for a specific entity.
  • + *
  • {@link #getVariantByName(String, String)} - Retrieves a specific {@link VariantParameter} by its name for a given entity.
  • + *
+ * + * @author MeAlam + * @since 1.0.0 + */ +public class VariantLoader implements IVariantEntityBase { + + /** + * A {@code private static final} {@link Map} to store entity variants as key-value pairs. + *

+ * This {@link Map} holds entity names and their corresponding list of {@link VariantParameter} instances. + *

+ * + * @since 1.0.0 + */ + private static final Map> entityVariantsMap = new HashMap<>(); + + /** + * A {@code private static final} {@link JSONLoader} to load JSON data from resources. + * + * @since 1.0.0 + */ + private static final JSONLoader jsonLoader = new JSONLoader(); + + /** + * A {@code private static final} {@link JSONMerger} to merge JSON data. + *

+ * This {@link JSONMerger} instance is used to merge JSON data into a single {@link JsonObject}. + *

+ * + * @since 1.0.0 + */ + private static final JSONMerger jsonMerger = new JSONMerger(); + + /** + * A {@code public static void} that loads and merges variant data from JSON resources in the specified folder path. + *

+ * The method loops through all resources in the folder and merges them into a single {@link JsonObject}.
+ * The merged JSON data is then parsed into {@link VariantParameter} instances and stored in {@link #entityVariantsMap}. + *

+ * + * @param pFolderPath {@link String} - The path to the folder containing JSON resources. + * @param pServer {@link MinecraftServer} - The {@link MinecraftServer} instance used to access resources. + * @param pEntityName {@link String} - The name of the entity whose variants should be cleared before loading new ones. + */ + public static void loadVariants(String pFolderPath, MinecraftServer pServer, String pEntityName) { + + clearVariantsForEntity(pEntityName); + + ResourceManager resourceManager = pServer.getResourceManager(); + JsonObject mergedJsonObject = new JsonObject(); + + Collection collection = resourceManager.listResources(pFolderPath, pFiles -> pFiles.getPath().endsWith(".json")).keySet(); + + BaseLogger.log(BaseLogLevel.INFO, "Found resources: " + collection + " at: " + pFolderPath + " for: " + pEntityName, true); + + for (ResourceLocation resourceLocation : collection) { + try { + BaseLogger.log(BaseLogLevel.INFO, "Loading JSON data from resource: " + resourceLocation.toString(), true); + JsonObject jsonObject = jsonLoader.loadJson(resourceLocation, resourceManager); + jsonMerger.mergeJsonObjects(mergedJsonObject, jsonObject); + } catch (Exception pException) { + BaseLogger.log(BaseLogLevel.ERROR, "Failed to load JSON data from resource: " + resourceLocation.toString(), pException, true); + } + } + parseVariants(mergedJsonObject); + } + + /** + * A {@code private static void} that clears variants for a specific entity type from {@link #entityVariantsMap}. + *

+ * This method removes all variants associated with the given entity name. + *

+ * + * @param pEntityName {@link String} - The name of the entity whose variants should be cleared. + */ + private static void clearVariantsForEntity(String pEntityName) { + entityVariantsMap.remove(pEntityName); + } + + /** + * A {@code private static void} that parses the merged JSON data and converts it into {@link VariantParameter} instances. + *

+ * This method processes each entry in the JSON object and stores the created {@link VariantParameter} instances in {@link #entityVariantsMap}. + *

+ * + * @param pJsonObject {@link JsonObject} - The merged {@link JsonObject} containing variant data. + */ + private static void parseVariants(JsonObject pJsonObject) { + for (Map.Entry entry : pJsonObject.entrySet()) { + String entityName = entry.getKey(); + JsonArray textureArray = entry.getValue().getAsJsonArray(); + + BaseLogger.log(BaseLogLevel.INFO, "Parsing variants for entity: " + entityName, true); + List variantList = entityVariantsMap.computeIfAbsent(entityName, k -> new ArrayList<>()); + + for (JsonElement variant : textureArray) { + VariantParameter newVariant = getEntityVariant(entityName, variant.getAsJsonObject()); + + boolean variantExists = variantList.stream() + .anyMatch(v -> v.equals(newVariant)); + + if (!variantExists) { + variantList.add(newVariant); + } + } + } + } + + /** + * A {@code private static} {@link VariantParameter} that creates a new {@link VariantParameter} instance from a JSON object. + *

+ * This method wraps the creation of {@link VariantParameter} instances for easier management and potential modification. + *

+ * + * @param pJsonKey {@link String} - The key associated with this variant. + * @param pJsonObject {@link JsonObject} - The {@link JsonObject} containing the variant data. + * @return {@link VariantParameter} - A {@link VariantParameter} instance. + */ + private static VariantParameter getEntityVariant(String pJsonKey, JsonObject pJsonObject) { + return new VariantParameter(pJsonKey, pJsonObject); + } + + /** + * A {@code public static} {@link List} that retrieves the {@link List} of loaded {@link VariantParameter} instances for a specific entity. + *

+ * This method returns a list of variants for the given entity name. If no variants are found, an empty list is returned. + *

+ * + * @param pEntityName {@link String} - The name of the entity to retrieve variants for. + * @return {@link List} - A {@link List} of {@link VariantParameter} instances for the specified entity. + */ + public static List getVariantsFromEntity(String pEntityName) { + BaseLogger.log(BaseLogLevel.INFO, "Retrieving variants for entity: " + pEntityName, true); + return entityVariantsMap.getOrDefault(pEntityName, new ArrayList<>()); + } + + /** + * A {@code public static} {@link VariantParameter} that retrieves a {@link VariantParameter} for a specific entity, by the variant's name. + *

+ * This method searches for a variant with the specified name within the list of variants for the given entity. + *

+ * + * @param pEntityName {@link String} - The name of the entity to retrieve variants for. + * @param pVariantName {@link String} - The name of the variant to retrieve. + * @return {@link VariantParameter} - The {@link VariantParameter} with the specified name, or {@code null} if not found. + */ + public static VariantParameter getVariantByName(String pEntityName, String pVariantName) { + BaseLogger.log(BaseLogLevel.INFO, "Retrieving variant by name: " + pVariantName + " for entity: " + pEntityName, true); + List variants = getVariantsFromEntity(pEntityName); + for (VariantParameter variant : variants) { + if (variant.getVariantParameter().equals(pVariantName)) { + return variant; + } + } + BaseLogger.log(BaseLogLevel.INFO, "Variant with name: " + pVariantName + " not found for entity: " + pEntityName, true); + return null; + } +} diff --git a/fabric/src/main/java/software/bluelib/entity/variant/VariantParameter.java b/fabric/src/main/java/software/bluelib/entity/variant/VariantParameter.java new file mode 100644 index 00000000..a3805dba --- /dev/null +++ b/fabric/src/main/java/software/bluelib/entity/variant/VariantParameter.java @@ -0,0 +1,174 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.entity.variant; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import software.bluelib.entity.variant.base.ParameterBase; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +import java.util.Map; +import java.util.Set; + +/** + * A {@code class} that represents the parameters associated with a specific variant of an entity. + *

+ * This class extends {@link ParameterBase} to store and manage variant-specific parameters parsed from a {@link JsonObject}. + *

+ * The class handles various JSON element types, including {@code JsonPrimitive}, {@code JsonArray}, and {@code JsonObject}. + *

+ * Key Methods: + *
    + *
  • {@link #getJsonKey()} - Retrieves the key of the JSON object that identifies this entity.
  • + *
  • {@link #getVariantParameter()} - Retrieves the name of the variant.
  • + *
  • {@link #getParameter(String)} - Retrieves the value of a specific parameter by its key.
  • + *
+ * + * @author MeAlam + * @since 1.0.0 + */ +public class VariantParameter extends ParameterBase { + + /** + * A {@code private final} {@link String} that represents the key of the JSON object that identifies this entity. + *

+ * This key is used to map the entity to its corresponding parameters within a {@link JsonObject}. + *

+ * + * @since 1.0.0 + */ + private final String jsonKey; + + /** + * A {@code private static} {@link String} that represents the name of the Variant parameter. + *

+ * This key is used to locate the variant name within the parameters/JSON files. + *

+ * + * @since 1.0.0 + */ + private static String variantParameterName = "variantName"; + + /** + * Constructs a new {@code VariantParameter} instance by extracting parameters from a given {@link JsonObject}. + *

+ * This constructor processes different types of {@link JsonElement} values: + *

    + *
  • {@link com.google.gson.JsonPrimitive}: Stored directly as a string.
  • + *
  • {@link com.google.gson.JsonArray}: Converts array elements into a single comma-separated string.
  • + *
  • {@link JsonObject}: Converts the nested JSON object to a string representation.
  • + *
  • {@code Other Types}: Stores "null" for unhandled JSON types.
  • + *
+ * + * @param pJsonKey {@link String} - The key that identifies this entity within the {@link JsonObject}. + * @param pJsonObject {@link JsonObject} - The {@link JsonObject} containing the variant parameters. + * @throws IllegalArgumentException if {@code pJsonKey} or {@code pJsonObject} is {@code null}. + * @author MeAlam + * @see ParameterBase + * @since 1.0.0 + */ + public VariantParameter(String pJsonKey, JsonObject pJsonObject) { + if (pJsonKey == null || pJsonObject == null) { + Throwable throwable = new Throwable("JSON key or JSON object is null"); + IllegalArgumentException exception = new IllegalArgumentException("JSON key and object must not be null"); + BaseLogger.log(BaseLogLevel.ERROR, exception.toString(), throwable, true); + throw exception; + } + this.jsonKey = pJsonKey; + BaseLogger.log(BaseLogLevel.INFO, "Creating VariantParameter with JSON key: " + pJsonKey, true); + Set> entryMap = pJsonObject.entrySet(); + for (Map.Entry entry : entryMap) { + JsonElement element = entry.getValue(); + if (element.isJsonPrimitive()) { + addParameter(entry.getKey(), element.getAsString()); + BaseLogger.log(BaseLogLevel.SUCCESS, "Added primitive parameter: " + entry.getKey() + " = " + element.getAsString(), true); + } else if (element.isJsonArray()) { + StringBuilder arrayValues = new StringBuilder(); + element.getAsJsonArray().forEach(e -> arrayValues.append(e.getAsString()).append(",")); + if (!arrayValues.isEmpty()) { + arrayValues.setLength(arrayValues.length() - 1); + } + addParameter(entry.getKey(), arrayValues.toString()); + BaseLogger.log(BaseLogLevel.SUCCESS, "Added array parameter: " + entry.getKey() + " = " + arrayValues, true); + } else if (element.isJsonObject()) { + addParameter(entry.getKey(), element.toString()); + BaseLogger.log(BaseLogLevel.SUCCESS, "Added object parameter: " + entry.getKey() + " = " + element, true); + } else { + addParameter(entry.getKey(), "null"); + BaseLogger.log(BaseLogLevel.SUCCESS, "Added null parameter for key: " + entry.getKey(), true); + } + } + } + + /** + * A {@link String} method that retrieves the key of the {@link JsonObject} that identifies this entity. + *

+ * This key is used to retrieve or map the entity within a broader data structure. + *

+ * + * @return The key of the JSON object representing this entity. + * @throws IllegalStateException if the key is unexpectedly {@code null}. + * @author MeAlam + * @since 1.0.0 + */ + public String getJsonKey() { + if (this.jsonKey == null) { + Throwable throwable = new Throwable("JSON key should not be null"); + IllegalStateException exception = new IllegalStateException("JSON key is unexpectedly null when retrieving from VariantParameter."); + BaseLogger.log(BaseLogLevel.ERROR, "JSON key is unexpectedly null when retrieving from VariantParameter.", throwable, true); + throw exception; + } + BaseLogger.log(BaseLogLevel.INFO, "Retrieved JSON key: " + this.jsonKey, true); + return this.jsonKey; + } + + /** + * A {@link String} method that retrieves the name of the variant parameter. + *

+ * The variant name is, by default, stored under the key {@code "variantName"} in the parameters/JSON files. + * Otherwise, the key is stored in the {@link #variantParameterName} field. + *

+ * + * @return The name of the variant, or {@code null} if the variant name is not found. + * @author MeAlam + * @since 1.0.0 + */ + public String getVariantParameter() { + String variantName = getParameter(variantParameterName); + BaseLogger.log(BaseLogLevel.INFO, "Retrieved parameter name: " + variantName, true); + return variantName; + } + + /** + * A {@link String} method that sets the name of the variant parameter. + *

+ * This method allows the user to customize the key used to locate the variant name within the parameters/JSON files. + *

+ * + * @param pCustomVariantName {@link String} - The custom name of the variant parameter. + * @author MeAlam + * @since 1.0.0 + */ + public void setVariantParameter(String pCustomVariantName) { + variantParameterName = pCustomVariantName; + BaseLogger.log(BaseLogLevel.INFO, "Setting parameter name: " + variantParameterName, true); + } + + /** + * A {@link String} method that retrieves the value of a specific parameter by its key. + *

+ * This method looks up the parameter's value within the internal data structure. + *

+ * + * @param pKey {@link String} - The key of the parameter to retrieve. + * @return The value of the parameter, or {@code null} if the key does not exist. + * @author MeAlam + * @since 1.0.0 + */ + public String getParameter(String pKey) { + String value = (String) super.getParameter(pKey); + BaseLogger.log(BaseLogLevel.INFO, "Retrieved parameter for key " + pKey + ": " + value, true); + return value; + } +} diff --git a/fabric/src/main/java/software/bluelib/entity/variant/base/ParameterBase.java b/fabric/src/main/java/software/bluelib/entity/variant/base/ParameterBase.java new file mode 100644 index 00000000..8e62626e --- /dev/null +++ b/fabric/src/main/java/software/bluelib/entity/variant/base/ParameterBase.java @@ -0,0 +1,211 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.entity.variant.base; + +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * A {@code public abstract base class} for managing a collection of {@link #parameters}. + *

+ * This {@code class} provides methods to add, retrieve, remove, and manipulate {@link #parameters} stored as key-value pairs. + *

+ * Key Methods: + *
    + *
  • {@link #addParameter(String, Object)} - Adds a parameter to {@link #parameters}.
  • + *
  • {@link #getParameter(String)} - Retrieves a parameter from {@link #parameters}.
  • + *
  • {@link #removeParameter(String)} - Removes a parameter from {@link #parameters}.
  • + *
  • {@link #getAllParameters()} - Returns all parameters in {@link #parameters}.
  • + *
  • {@link #containsParameter(String)} - Checks if a parameter exists by its key from {@link #parameters}.
  • + *
  • {@link #isEmpty()} - Checks if {@link #parameters} is empty.
  • + *
  • {@link #clearParameters()} - Clears all parameters from {@link #parameters}.
  • + *
  • {@link #getParameterCount()} - Returns the number of parameters in {@link #parameters}.
  • + *
  • {@link #getParameterKeys()} - Returns a set of all parameter keys from {@link #parameters}.
  • + *
  • {@link #getParameterValues()} - Returns a collection of all parameter values from {@link #parameters}.
  • + *
  • {@link #updateParameter(String, Object)} - Updates the value of an existing parameter in {@link #parameters}.
  • + *
+ * + * @author MeAlam + * @since 1.0.0 + */ +public abstract class ParameterBase { + + /** + * A {@code private final} {@link Map} to store parameters as key-value pairs. + *

+ * This {@link Map} holds parameter keys and their corresponding values. + *

+ * + * @since 1.0.0 + */ + private final Map parameters = new HashMap<>(); + + /** + * A {@code protected void} that adds a parameter to {@link #parameters}. + *

+ * This method stores a new parameter with the specified key and value in {@link #parameters}. + *

+ * + * @param pKey {@link String} - The key under which the parameter is stored. + * @param pValue {@link Object} - The value of the parameter. + * @author MeAlam + * @since 1.0.0 + */ + protected void addParameter(String pKey, Object pValue) { + parameters.put(pKey, pValue); + } + + /** + * A {@code protected} {@link Object} that retrieves a parameter from {@link #parameters} by its key. + *

+ * This method returns the value associated with the specified key, or {@code null} if the key does not exist. + *

+ * + * @param pKey {@link String} - The key of the parameter to retrieve. + * @return {@link Object} - The value associated with the key, or {@code null} if the key does not exist. + * @author MeAlam + * @since 1.0.0 + */ + protected Object getParameter(String pKey) { + return parameters.get(pKey); + } + + /** + * A {@code protected void} that removes a parameter from {@link #parameters} by its key. + *

+ * This method deletes the parameter with the specified key from {@link #parameters}. If the key does not exist, no action is taken. + *

+ * + * @param pKey {@link String} - The key of the parameter to remove. + * @author MeAlam + * @since 1.0.0 + */ + protected void removeParameter(String pKey) { + if (parameters.remove(pKey) != null) { + BaseLogger.log(BaseLogLevel.SUCCESS, String.format("Parameter removed: Key = %s", pKey), true); + } else { + BaseLogger.log(BaseLogLevel.WARNING, String.format("Attempted to remove non-existent parameter: Key = %s", pKey), true); + } + } + + /** + * A {@code protected} {@link Map} that returns all parameters in {@link #parameters}. + *

+ * This method returns a new {@link Map} containing all parameters stored in {@link #parameters}. + *

+ * + * @return {@link Map} - A {@link Map} containing all parameters. + * @author MeAlam + * @since 1.0.0 + */ + protected Map getAllParameters() { + return new HashMap<>(parameters); + } + + /** + * A {@code protected} {@link Boolean} that checks if a parameter exists by its key. + *

+ * This method returns {@code true} if the parameter with the specified key exists in {@link #parameters}, {@code false} otherwise. + *

+ * + * @param pKey {@link String} - The key of the parameter to check. + * @return {@link Boolean} - {@code true} if the parameter exists and {@code false} if it doesn't. + * @author MeAlam + * @since 1.0.0 + */ + protected boolean containsParameter(String pKey) { + return parameters.containsKey(pKey); + } + + /** + * A {@code protected} {@link Boolean} that checks if {@link #parameters} is empty. + *

+ * This method returns {@code true} if {@link #parameters} contains no parameters, {@code false} otherwise. + *

+ * + * @return {@link Boolean} - {@code true} if {@link #parameters} is empty and {@code false} if it isn't. + * @author MeAlam + * @since 1.0.0 + */ + protected boolean isEmpty() { + return parameters.isEmpty(); + } + + /** + * A {@code protected void} that removes all parameters from {@link #parameters}. + * + * @author MeAlam + * @since 1.0.0 + */ + protected void clearParameters() { + parameters.clear(); + } + + /** + * A {@code protected} {@link Integer} that returns the number of parameters in {@link #parameters}. + * + * @return {@link Integer} - The number of parameters in the collection. + * @author MeAlam + * @since 1.0.0 + */ + protected int getParameterCount() { + return parameters.size(); + } + + /** + * A {@code protected} {@link Set} that returns a set of all parameter keys. + *

+ * This method provides a {@link Set} containing all the keys of parameters in {@link #parameters}. + *

+ * + * @return {@link Set} - A {@link Set} containing all parameter keys. + * @author MeAlam + * @since 1.0.0 + */ + protected Set getParameterKeys() { + return parameters.keySet(); + } + + /** + * A {@code protected} {@link Collection} that returns a {@link Collection} of all parameter values. + *

+ * This method provides a {@link Collection} containing all the values of parameters in {@link #parameters}. + *

+ * + * @return {@link Collection} - A {@link Collection} containing all parameter values. + * @author MeAlam + * @since 1.0.0 + */ + protected Collection getParameterValues() { + return parameters.values(); + } + + /** + * A {@code protected void} that updates the value of an existing parameter. + *

+ * This method changes the value of a parameter in {@link #parameters} that is identified by the specified key. If the key does not exist, an exception is thrown. + *

+ * + * @param pKey {@link String} - The key of the parameter to update. + * @param pNewValue {@link Object} - The new value to set for the parameter. + * @throws IllegalArgumentException if the key does not exist. + * @author MeAlam + * @since 1.0.0 + */ + protected void updateParameter(String pKey, Object pNewValue) { + if (parameters.containsKey(pKey)) { + parameters.put(pKey, pNewValue); + BaseLogger.log(BaseLogLevel.SUCCESS, String.format("Parameter updated: Key = %s, New Value = %s", pKey, pNewValue), true); + } else { + Throwable throwable = new Throwable("Key does not exist: " + pKey); + IllegalArgumentException exception = new IllegalArgumentException("Key does not exist: " + pKey); + BaseLogger.log(BaseLogLevel.ERROR, String.format("Attempted to update non-existent parameter: Key = %s", pKey), throwable, true); + throw exception; + } + } +} diff --git a/fabric/src/main/java/software/bluelib/event/ReloadEventHandler.java b/fabric/src/main/java/software/bluelib/event/ReloadEventHandler.java new file mode 100644 index 00000000..490c730b --- /dev/null +++ b/fabric/src/main/java/software/bluelib/event/ReloadEventHandler.java @@ -0,0 +1,79 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.event; + +import com.google.gson.JsonParseException; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import software.bluelib.entity.variant.VariantLoader; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +/** + * A {@code class} responsible for handling events related to reloading entity variants. + *

+ * This class provides functionality to register entity variants from specified locations when the server starts. + *

+ *

+ * Key Features: + *

    + *
  • {@link #registerEntityVariants(String, MinecraftServer, String, String)} - Registers entity variants from specified locations.
  • + *
+ * + * @author MeAlam + * @see VariantLoader + * @see MinecraftServer + * @see ResourceLocation + * @since 1.0.0 + */ +public class ReloadEventHandler { + + /** + * A {@code protected static void} that registers entity variants from specified locations. + *

+ * This method attempts to load variants from both mod and datapack locations. It logs status information and + * handles exceptions that occur during the loading process. + *

+ *

+ * Parameters: + *

    + *
  • {@code pFolderPath} {@link String} - The folder path location within the mod or datapack where variants are stored.
  • + *
  • {@code pServer} {@link MinecraftServer} - The server instance of the current world.
  • + *
  • {@code pModID} {@link String} - The mod ID used to locate the entity variant resources. (Use your Mod's ID)
  • + *
  • {@code pEntityName} {@link String} - The entity name to load.
  • + *
+ * + * Exception Handling: + *
    + *
  • {@link JsonParseException} - Thrown when there is an error parsing the JSON files.
  • + *
  • {@link RuntimeException} - Thrown for unexpected errors during the registration process.
  • + *
+ * + * @param pFolderPath {@link String} - The folder path location within the mod or datapack where variants are stored. + * @param pServer {@link MinecraftServer} - The server instance of the current world. + * @param pModID {@link String} - The mod ID used to locate the entity variant resources. (Use your Mod's ID) + * @param pEntityName {@link String} - The entity name to load. + * @throws JsonParseException if there is an error parsing the JSON files. + * @throws RuntimeException if an unexpected error occurs during the registration process. + * @author MeAlam + * @see MinecraftServer + * @see ResourceLocation + * @see VariantLoader + * @since 1.0.0 + */ + protected static void registerEntityVariants(String pFolderPath, MinecraftServer pServer, String pModID, String pEntityName) { + + BaseLogger.log(BaseLogLevel.INFO, "Attempting to register entity variants for " + pEntityName + " with ModID: " + pModID, true); + + try { + VariantLoader.loadVariants(pFolderPath, pServer, pEntityName); + BaseLogger.log(BaseLogLevel.SUCCESS, "Successfully registered entity variants for " + pEntityName + " from ModID: " + pModID, true); + } catch (JsonParseException pException) { + BaseLogger.log(BaseLogLevel.ERROR, "Failed to parse JSON(s) while registering entity variants for " + pEntityName + " from ModID: " + pModID, pException, true); + throw pException; + } catch (Exception pException) { + BaseLogger.log(BaseLogLevel.ERROR, "Unexpected error occurred while registering entity variants for " + pEntityName + " from ModID: " + pModID, pException, true); + throw pException; + } + } +} diff --git a/NeoForge/src/main/java/software/bluelib/example/entity/dragon/DragonEntity.java b/fabric/src/main/java/software/bluelib/example/entity/dragon/DragonEntity.java similarity index 77% rename from NeoForge/src/main/java/software/bluelib/example/entity/dragon/DragonEntity.java rename to fabric/src/main/java/software/bluelib/example/entity/dragon/DragonEntity.java index 00e15560..413ca41a 100644 --- a/NeoForge/src/main/java/software/bluelib/example/entity/dragon/DragonEntity.java +++ b/fabric/src/main/java/software/bluelib/example/entity/dragon/DragonEntity.java @@ -11,6 +11,7 @@ import net.minecraft.world.entity.*; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import net.minecraft.world.level.ServerLevelAccessor; import org.jetbrains.annotations.NotNull; @@ -20,7 +21,9 @@ import software.bernie.geckolib.core.animation.AnimatableManager; import software.bernie.geckolib.util.GeckoLibUtil; import software.bluelib.interfaces.variant.IVariantEntity; -import software.bluelib.utils.ParameterUtils; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; +import software.bluelib.utils.variant.ParameterUtils; /** * A {@code DragonEntity} class representing a dragon entity in the game, which extends {@link TamableAnimal} @@ -29,8 +32,6 @@ * This class manages the dragon's variant system, its data synchronization, and integrates with the GeckoLib * animation system. *

- * - *

* Key Methods: *

    *
  • {@link #defineSynchedData()} - Defines the synchronized data for the dragon entity, including its variant.
  • @@ -40,10 +41,8 @@ *
  • {@link #setVariantName(String)} - Sets the variant name of the dragon.
  • *
  • {@link #getVariantName()} - Retrieves the current variant name of the dragon.
  • *
- *

* * @author MeAlam - * @Co-author Dan * @since 1.0.0 */ public class DragonEntity extends TamableAnimal implements IVariantEntity, GeoEntity { @@ -52,14 +51,14 @@ public class DragonEntity extends TamableAnimal implements IVariantEntity, GeoEn *

* This is used to store and retrieve the variant data for synchronization between server and client. *

- * @Co-author MeAlam, Dan + * * @since 1.0.0 */ public static final EntityDataAccessor VARIANT = SynchedEntityData.defineId(DragonEntity.class, EntityDataSerializers.STRING); /** * The name of the entity. - * @Co-author MeAlam, Dan + * * @since 1.0.0 */ protected final String entityName = "dragon"; @@ -68,11 +67,9 @@ public class DragonEntity extends TamableAnimal implements IVariantEntity, GeoEn * Constructs a new {@link DragonEntity} instance with the specified entity type and level. * * @param pEntityType {@link EntityType} - The type of the entity. - * @param pLevel {@link Level} - The level in which the entity is created. - * - * @since 1.0.0 + * @param pLevel {@link Level} - The level in which the entity is created. * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ public DragonEntity(EntityType pEntityType, Level pLevel) { super(pEntityType, pLevel); @@ -84,9 +81,8 @@ public DragonEntity(EntityType pEntityType, Level pLeve * This method initializes the {@link EntityDataAccessor} to handle the variant data. *

* - * @since 1.0.0 * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ @Override protected void defineSynchedData() { @@ -101,10 +97,8 @@ protected void defineSynchedData() { *

* * @param pCompound {@link CompoundTag} - The NBT tag to which data should be added. - * - * @since 1.0.0 * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ @Override public void addAdditionalSaveData(@NotNull CompoundTag pCompound) { @@ -119,10 +113,8 @@ public void addAdditionalSaveData(@NotNull CompoundTag pCompound) { *

* * @param pCompound {@link CompoundTag} - The NBT tag from which data should be read. - * - * @since 1.0.0 * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ @Override public void readAdditionalSaveData(@NotNull CompoundTag pCompound) { @@ -136,39 +128,35 @@ public void readAdditionalSaveData(@NotNull CompoundTag pCompound) { * This method sets up the variant for the entity and connects parameters if needed. *

* - * @param pLevel {@link ServerLevelAccessor} - The level in which the entity is spawned. + * @param pLevel {@link ServerLevelAccessor} - The level in which the entity is spawned. * @param pDifficulty {@link DifficultyInstance} - The difficulty instance for spawning. - * @param pReason {@link MobSpawnType} - The reason for spawning the entity. - * @param pSpawnData {@link SpawnGroupData} - Data related to the spawn. - * @param pDataTag {@link CompoundTag} - Additional data for spawning. + * @param pReason {@link MobSpawnType} - The reason for spawning the entity. + * @param pSpawnData {@link SpawnGroupData} - Data related to the spawn. * @return {@link SpawnGroupData} - Updated spawn data. - * - * @since 1.0.0 * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ @Override - public SpawnGroupData finalizeSpawn(@NotNull ServerLevelAccessor pLevel, @NotNull DifficultyInstance pDifficulty, @NotNull MobSpawnType pReason, @Nullable SpawnGroupData pSpawnData, @Nullable CompoundTag pDataTag) { + public SpawnGroupData finalizeSpawn(@NotNull ServerLevelAccessor pLevel, @NotNull DifficultyInstance pDifficulty, @NotNull MobSpawnType pReason, @Nullable SpawnGroupData pSpawnData, @Nullable CompoundTag pSpawnTag) { if (getVariantName() == null || getVariantName().isEmpty()) { this.setVariantName(getRandomVariant(getEntityVariants(entityName), "normal")); - ParameterUtils.ParameterBuilder.forVariant(entityName,this.getVariantName()) + ParameterUtils.ParameterBuilder.forVariant(entityName, this.getVariantName()) .withParameter("customParameter") .withParameter("int") .withParameter("bool") .withParameter("array") .connect(); } - return super.finalizeSpawn(pLevel, pDifficulty, pReason, pSpawnData, pDataTag); + BaseLogger.log(BaseLogLevel.SUCCESS, "Dragon Spawned with Variant: " + getVariantName(), true); + return super.finalizeSpawn(pLevel, pDifficulty, pReason, pSpawnData, pSpawnTag); } /** * Sets the variant name for the dragon entity. * * @param pName {@link String} - The name of the variant to set. - * - * @since 1.0.0 * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ public void setVariantName(String pName) { this.entityData.set(VARIANT, pName); @@ -178,21 +166,28 @@ public void setVariantName(String pName) { * Retrieves the current variant name of the dragon entity. * * @return {@link String} - The current variant name. - * - * @since 1.0.0 * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ public String getVariantName() { return this.entityData.get(VARIANT); } + /* All Code below this Fragment is not Library Related!!! */ /** - * All Code below this Fragment is not Library Related!!! + * The cache for the animatable instance. + * + * @since 1.0.0 */ - private final AnimatableInstanceCache cache = GeckoLibUtil.createInstanceCache(this); + /** + * Defines the synchronized data for the dragon entity. + * + * @return {@link SynchedEntityData} - The builder for the synchronized data. + * @author MeAlam + * @since 1.0.0 + */ public static AttributeSupplier.Builder createAttributes() { return Mob.createMobAttributes() .add(Attributes.MOVEMENT_SPEED, 0.3) @@ -203,18 +198,54 @@ public static AttributeSupplier.Builder createAttributes() { .add(Attributes.FLYING_SPEED, 0.3); } + /** + * Adds custom data to the entity's NBT for saving. + * + * @param pControllerRegistrar {@link CompoundTag} - The tag to add the data to. + * @author MeAlam + * @since 1.0.0 + */ @Override public void registerControllers(AnimatableManager.ControllerRegistrar pControllerRegistrar) { } + /** + * Adds custom data to the entity's NBT for saving. + * + * @return {@link CompoundTag} - The tag with the custom data. + * @author MeAlam + * @since 1.0.0 + */ @Override public AnimatableInstanceCache getAnimatableInstanceCache() { return cache; } + /** + * Adds custom data to the entity's NBT for saving. + * + * @param pLevel {@link CompoundTag} - The tag to add the data to. + * @param pOtherParent {@link CompoundTag} - The other tag to add the data from. + * @return {@link CompoundTag} - The tag with the custom data. + * @author MeAlam + * @since 1.0.0 + */ @Nullable @Override public AgeableMob getBreedOffspring(@NotNull ServerLevel pLevel, @NotNull AgeableMob pOtherParent) { return null; } + + /** + * Adds custom data to the entity's NBT for saving. + * + * @param pItemStack {@link ItemStack} - The item stack to check. + * @return {@link boolean} - Whether the item is food or not. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public boolean isFood(@NotNull ItemStack pItemStack) { + return false; + } } diff --git a/fabric/src/main/java/software/bluelib/example/entity/dragon/DragonModel.java b/fabric/src/main/java/software/bluelib/example/entity/dragon/DragonModel.java new file mode 100644 index 00000000..d5f85626 --- /dev/null +++ b/fabric/src/main/java/software/bluelib/example/entity/dragon/DragonModel.java @@ -0,0 +1,59 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.example.entity.dragon; + +import net.minecraft.resources.ResourceLocation; +import software.bernie.geckolib.model.GeoModel; +import software.bluelib.BlueLibConstants; + +/** + * A {@code public class} that extends {@link GeoModel} for the {@link DragonEntity} entity. + * Key Methods: + *
    + *
  • {@link #getModelResource(DragonEntity)} - Get the Model Location.
  • + *
  • {@link #getTextureResource(DragonEntity)} - Get the Texture Location.
  • + *
  • {@link #getAnimationResource(DragonEntity)} - Get the Animation Location.
  • + *
+ */ +public class DragonModel extends GeoModel { + + + /** + * A {@code public} {@link ResourceLocation} method that returns the location of the model. + * + * @param pObject {@link DragonEntity} - The entity to get the model for. + * @return {@link ResourceLocation} - The location of the model. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public ResourceLocation getModelResource(DragonEntity pObject) { + return new ResourceLocation(BlueLibConstants.MOD_ID, "geo/dragon.geo.json"); + } + + /** + * A {@code public} {@link ResourceLocation} method that returns the location of the texture. + * + * @param pObject {@link DragonEntity} - The entity to get the texture for. + * @return {@link ResourceLocation} - The location of the texture. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public ResourceLocation getTextureResource(DragonEntity pObject) { + return pObject.getTextureLocation(BlueLibConstants.MOD_ID, "textures/entity/" + pObject.entityName + "/" + pObject.getVariantName() + ".png"); + } + + /** + * A {@code public} {@link ResourceLocation} method that returns the location of the animation. + * + * @param pAnimatable {@link DragonEntity} - The entity to get the animation for. + * @return {@link ResourceLocation} - The location of the animation. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public ResourceLocation getAnimationResource(DragonEntity pAnimatable) { + return new ResourceLocation(BlueLibConstants.MOD_ID, "animations/dragon.animation.json"); + } +} diff --git a/NeoForge/src/main/java/software/bluelib/example/entity/dragon/DragonRender.java b/fabric/src/main/java/software/bluelib/example/entity/dragon/DragonRender.java similarity index 57% rename from NeoForge/src/main/java/software/bluelib/example/entity/dragon/DragonRender.java rename to fabric/src/main/java/software/bluelib/example/entity/dragon/DragonRender.java index c770ac48..d6a4e340 100644 --- a/NeoForge/src/main/java/software/bluelib/example/entity/dragon/DragonRender.java +++ b/fabric/src/main/java/software/bluelib/example/entity/dragon/DragonRender.java @@ -5,9 +5,21 @@ import net.minecraft.client.renderer.entity.EntityRendererProvider; import software.bernie.geckolib.renderer.GeoEntityRenderer; +/** + * A {@code public class} that extends {@link GeoEntityRenderer} for rendering the dragon entity. + * + * @author MeAlam + * @since 1.0.0 + */ public class DragonRender extends GeoEntityRenderer { - // Render the entity + /** + * Constructor + * + * @param pRenderManager {@link EntityRendererProvider.Context} - The render manager. + * @author MeAlam + * @since 1.0.0 + */ public DragonRender(EntityRendererProvider.Context pRenderManager) { super(pRenderManager, new DragonModel()); } diff --git a/NeoForge/src/main/java/software/bluelib/example/entity/rex/RexEntity.java b/fabric/src/main/java/software/bluelib/example/entity/rex/RexEntity.java similarity index 76% rename from NeoForge/src/main/java/software/bluelib/example/entity/rex/RexEntity.java rename to fabric/src/main/java/software/bluelib/example/entity/rex/RexEntity.java index fcb85a52..270e9025 100644 --- a/NeoForge/src/main/java/software/bluelib/example/entity/rex/RexEntity.java +++ b/fabric/src/main/java/software/bluelib/example/entity/rex/RexEntity.java @@ -11,6 +11,7 @@ import net.minecraft.world.entity.*; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import net.minecraft.world.level.ServerLevelAccessor; import org.jetbrains.annotations.NotNull; @@ -20,17 +21,17 @@ import software.bernie.geckolib.core.animation.AnimatableManager; import software.bernie.geckolib.util.GeckoLibUtil; import software.bluelib.interfaces.variant.IVariantEntity; -import software.bluelib.utils.ParameterUtils; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; +import software.bluelib.utils.variant.ParameterUtils; /** - * A {@code RexEntity} class representing a Rex entity in the game, which extends {@link TamableAnimal} + * A {@code rexEntity} class representing a rex entity in the game, which extends {@link TamableAnimal} * and implements {@link IVariantEntity} and {@link GeoEntity}. *

* This class manages the rex's variant system, its data synchronization, and integrates with the GeckoLib * animation system. *

- * - *

* Key Methods: *

    *
  • {@link #defineSynchedData()} - Defines the synchronized data for the rex entity, including its variant.
  • @@ -40,10 +41,8 @@ *
  • {@link #setVariantName(String)} - Sets the variant name of the rex.
  • *
  • {@link #getVariantName()} - Retrieves the current variant name of the rex.
  • *
- *

* * @author MeAlam - * @Co-author Dan * @since 1.0.0 */ public class RexEntity extends TamableAnimal implements IVariantEntity, GeoEntity { @@ -52,14 +51,14 @@ public class RexEntity extends TamableAnimal implements IVariantEntity, GeoEntit *

* This is used to store and retrieve the variant data for synchronization between server and client. *

- * @Co-author MeAlam, Dan + * * @since 1.0.0 */ public static final EntityDataAccessor VARIANT = SynchedEntityData.defineId(RexEntity.class, EntityDataSerializers.STRING); /** * The name of the entity. - * @Co-author MeAlam, Dan + * * @since 1.0.0 */ protected final String entityName = "rex"; @@ -68,11 +67,9 @@ public class RexEntity extends TamableAnimal implements IVariantEntity, GeoEntit * Constructs a new {@link RexEntity} instance with the specified entity type and level. * * @param pEntityType {@link EntityType} - The type of the entity. - * @param pLevel {@link Level} - The level in which the entity is created. - * - * @since 1.0.0 + * @param pLevel {@link Level} - The level in which the entity is created. * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ public RexEntity(EntityType pEntityType, Level pLevel) { super(pEntityType, pLevel); @@ -84,9 +81,8 @@ public RexEntity(EntityType pEntityType, Level pLevel) * This method initializes the {@link EntityDataAccessor} to handle the variant data. *

* - * @since 1.0.0 * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ @Override protected void defineSynchedData() { @@ -101,10 +97,8 @@ protected void defineSynchedData() { *

* * @param pCompound {@link CompoundTag} - The NBT tag to which data should be added. - * - * @since 1.0.0 * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ @Override public void addAdditionalSaveData(@NotNull CompoundTag pCompound) { @@ -119,10 +113,8 @@ public void addAdditionalSaveData(@NotNull CompoundTag pCompound) { *

* * @param pCompound {@link CompoundTag} - The NBT tag from which data should be read. - * - * @since 1.0.0 * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ @Override public void readAdditionalSaveData(@NotNull CompoundTag pCompound) { @@ -136,39 +128,35 @@ public void readAdditionalSaveData(@NotNull CompoundTag pCompound) { * This method sets up the variant for the entity and connects parameters if needed. *

* - * @param pLevel {@link ServerLevelAccessor} - The level in which the entity is spawned. + * @param pLevel {@link ServerLevelAccessor} - The level in which the entity is spawned. * @param pDifficulty {@link DifficultyInstance} - The difficulty instance for spawning. - * @param pReason {@link MobSpawnType} - The reason for spawning the entity. - * @param pSpawnData {@link SpawnGroupData} - Data related to the spawn. - * @param pDataTag {@link CompoundTag} - Additional data for spawning. + * @param pReason {@link MobSpawnType} - The reason for spawning the entity. + * @param pSpawnData {@link SpawnGroupData} - Data related to the spawn. * @return {@link SpawnGroupData} - Updated spawn data. - * - * @since 1.0.0 * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ @Override - public SpawnGroupData finalizeSpawn(@NotNull ServerLevelAccessor pLevel, @NotNull DifficultyInstance pDifficulty, @NotNull MobSpawnType pReason, @Nullable SpawnGroupData pSpawnData, @Nullable CompoundTag pDataTag) { + public SpawnGroupData finalizeSpawn(@NotNull ServerLevelAccessor pLevel, @NotNull DifficultyInstance pDifficulty, @NotNull MobSpawnType pReason, @Nullable SpawnGroupData pSpawnData, @Nullable CompoundTag pSpawnTag) { if (getVariantName() == null || getVariantName().isEmpty()) { this.setVariantName(getRandomVariant(getEntityVariants(entityName), "normal")); - ParameterUtils.ParameterBuilder.forVariant(entityName,this.getVariantName()) + ParameterUtils.ParameterBuilder.forVariant(entityName, this.getVariantName()) .withParameter("customParameter") .withParameter("int") .withParameter("bool") .withParameter("array") .connect(); } - return super.finalizeSpawn(pLevel, pDifficulty, pReason, pSpawnData, pDataTag); + BaseLogger.log(BaseLogLevel.SUCCESS, "Rex Spawned with Variant: " + getVariantName(), true); + return super.finalizeSpawn(pLevel, pDifficulty, pReason, pSpawnData, pSpawnTag); } /** * Sets the variant name for the rex entity. * * @param pName {@link String} - The name of the variant to set. - * - * @since 1.0.0 * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ public void setVariantName(String pName) { this.entityData.set(VARIANT, pName); @@ -178,21 +166,28 @@ public void setVariantName(String pName) { * Retrieves the current variant name of the rex entity. * * @return {@link String} - The current variant name. - * - * @since 1.0.0 * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ public String getVariantName() { return this.entityData.get(VARIANT); } + /* All Code below this Fragment is not Library Related!!! */ /** - * All Code below this Fragment is not Library Related!!! + * The cache for the animatable instance. + * + * @since 1.0.0 */ - private final AnimatableInstanceCache cache = GeckoLibUtil.createInstanceCache(this); + /** + * Defines the synchronized data for the rex entity. + * + * @return {@link SynchedEntityData} - The builder for the synchronized data. + * @author MeAlam + * @since 1.0.0 + */ public static AttributeSupplier.Builder createAttributes() { return Mob.createMobAttributes() .add(Attributes.MOVEMENT_SPEED, 0.3) @@ -203,18 +198,54 @@ public static AttributeSupplier.Builder createAttributes() { .add(Attributes.FLYING_SPEED, 0.3); } + /** + * Adds custom data to the entity's NBT for saving. + * + * @param pControllerRegistrar {@link CompoundTag} - The tag to add the data to. + * @author MeAlam + * @since 1.0.0 + */ @Override public void registerControllers(AnimatableManager.ControllerRegistrar pControllerRegistrar) { } + /** + * Adds custom data to the entity's NBT for saving. + * + * @return {@link CompoundTag} - The tag with the custom data. + * @author MeAlam + * @since 1.0.0 + */ @Override public AnimatableInstanceCache getAnimatableInstanceCache() { return cache; } + /** + * Adds custom data to the entity's NBT for saving. + * + * @param pLevel {@link CompoundTag} - The tag to add the data to. + * @param pOtherParent {@link CompoundTag} - The other tag to add the data from. + * @return {@link CompoundTag} - The tag with the custom data. + * @author MeAlam + * @since 1.0.0 + */ @Nullable @Override public AgeableMob getBreedOffspring(@NotNull ServerLevel pLevel, @NotNull AgeableMob pOtherParent) { return null; } + + /** + * Adds custom data to the entity's NBT for saving. + * + * @param pItemStack {@link ItemStack} - The item stack to check. + * @return {@link boolean} - Whether the item is food or not. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public boolean isFood(@NotNull ItemStack pItemStack) { + return false; + } } diff --git a/fabric/src/main/java/software/bluelib/example/entity/rex/RexModel.java b/fabric/src/main/java/software/bluelib/example/entity/rex/RexModel.java new file mode 100644 index 00000000..f408313b --- /dev/null +++ b/fabric/src/main/java/software/bluelib/example/entity/rex/RexModel.java @@ -0,0 +1,59 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.example.entity.rex; + +import net.minecraft.resources.ResourceLocation; +import software.bernie.geckolib.model.GeoModel; +import software.bluelib.BlueLibConstants; + +/** + * A {@code public class} that extends {@link GeoModel} for the {@link RexEntity} entity. + * Key Methods: + *
    + *
  • {@link #getModelResource(RexEntity)} - Get the Model Location.
  • + *
  • {@link #getTextureResource(RexEntity)} - Get the Texture Location.
  • + *
  • {@link #getAnimationResource(RexEntity)} - Get the Animation Location.
  • + *
+ */ +public class RexModel extends GeoModel { + + + /** + * A {@code public} {@link ResourceLocation} method that returns the location of the model. + * + * @param pObject {@link RexEntity} - The entity to get the model for. + * @return {@link ResourceLocation} - The location of the model. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public ResourceLocation getModelResource(RexEntity pObject) { + return new ResourceLocation(BlueLibConstants.MOD_ID, "geo/rex.geo.json"); + } + + /** + * A {@code public} {@link ResourceLocation} method that returns the location of the texture. + * + * @param pObject {@link RexEntity} - The entity to get the texture for. + * @return {@link ResourceLocation} - The location of the texture. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public ResourceLocation getTextureResource(RexEntity pObject) { + return pObject.getTextureLocation(BlueLibConstants.MOD_ID, "textures/entity/" + pObject.entityName + "/" + pObject.getVariantName() + ".png"); + } + + /** + * A {@code public} {@link ResourceLocation} method that returns the location of the animation. + * + * @param pAnimatable {@link RexEntity} - The entity to get the animation for. + * @return {@link ResourceLocation} - The location of the animation. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public ResourceLocation getAnimationResource(RexEntity pAnimatable) { + return new ResourceLocation(BlueLibConstants.MOD_ID, "animations/rex.animation.json"); + } +} diff --git a/NeoForge/src/main/java/software/bluelib/example/entity/rex/RexRender.java b/fabric/src/main/java/software/bluelib/example/entity/rex/RexRender.java similarity index 57% rename from NeoForge/src/main/java/software/bluelib/example/entity/rex/RexRender.java rename to fabric/src/main/java/software/bluelib/example/entity/rex/RexRender.java index 776c5920..2ca92fae 100644 --- a/NeoForge/src/main/java/software/bluelib/example/entity/rex/RexRender.java +++ b/fabric/src/main/java/software/bluelib/example/entity/rex/RexRender.java @@ -5,9 +5,21 @@ import net.minecraft.client.renderer.entity.EntityRendererProvider; import software.bernie.geckolib.renderer.GeoEntityRenderer; +/** + * A {@code public class} that extends {@link GeoEntityRenderer} for rendering the rex entity. + * + * @author MeAlam + * @since 1.0.0 + */ public class RexRender extends GeoEntityRenderer { - // Render the entity + /** + * Constructor + * + * @param pRenderManager {@link EntityRendererProvider.Context} - The render manager. + * @author MeAlam + * @since 1.0.0 + */ public RexRender(EntityRendererProvider.Context pRenderManager) { super(pRenderManager, new RexModel()); } diff --git a/fabric/src/main/java/software/bluelib/example/event/ReloadHandler.java b/fabric/src/main/java/software/bluelib/example/event/ReloadHandler.java new file mode 100644 index 00000000..b96a2d0c --- /dev/null +++ b/fabric/src/main/java/software/bluelib/example/event/ReloadHandler.java @@ -0,0 +1,126 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.example.event; + +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.minecraft.server.MinecraftServer; +import software.bluelib.BlueLibConstants; +import software.bluelib.event.ReloadEventHandler; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * A {@code ReloadHandler} class that handles server start and reload events related to entity variants. + *

+ * This class extends {@link ReloadEventHandler} and implements event handling for server starting and reloading, + * ensuring that entity variant data is properly loaded and refreshed. + *

+ * Key Methods: + *
    + *
  • {@link #onServerStart(MinecraftServer)} - Handles server starting events to initialize entity variants.
  • + *
  • {@link #onReload()} - Handles reload events to refresh entity variants.
  • + *
  • {@link #LoadEntityVariants(MinecraftServer)} - Loads entity variants from JSON files into the server.
  • + *
+ * + * @author MeAlam + * @since 1.0.0 + */ +public class ReloadHandler extends ReloadEventHandler { + + /** + * The {@link MinecraftServer} instance for the server handling the events. + *

+ * This is initialized when the server starts and used to load entity variants. + *

+ * + * @since 1.0.0 + */ + private static MinecraftServer server; + + /** + * Handles the server starting event to initialize the {@link MinecraftServer} instance + * and load entity variants. + * + * @param pServer {@link MinecraftServer} - The server triggered when the server starts. + * @author MeAlam + * @since 1.0.0 + */ + public static void onServerStart(MinecraftServer pServer) { + server = pServer; + ReloadHandler.LoadEntityVariants(server); + BaseLogger.log(BaseLogLevel.INFO, "Entity variants loaded.", true); + } + + /** + * Handles the reload event by scheduling a task to reload entity variants. + *

+ * This method schedules the {@code LoadEntityVariants} method to run after a short delay. + *

+ * + * @author MeAlam + * @since 1.0.0 + */ + public static void onReload() { + if (server != null) { + BlueLibConstants.SCHEDULER.schedule(() -> { + server.execute(() -> { + ReloadHandler.LoadEntityVariants(server); + BaseLogger.log(BaseLogLevel.INFO, "Entity variants reloaded.", true); + }); + }, 1, TimeUnit.SECONDS); + } + } + + /** + * The base path for entity variant JSON files. + *

+ * This path is used to locate the files that contain variant data for entities. + *

+ * + * @since 1.0.0 + */ + private static final String basePath = "variant/entity/"; + + /** + * A {@link List} of entity names for which variants will be loaded. + *

+ * This list defines which entities will have their variants loaded from JSON files. + *

+ * + * @since 1.0.0 + */ + private static final List entityNames = Arrays.asList("dragon", "rex"); + + /** + * Loads entity variants from JSON files into the {@link MinecraftServer}. + *

+ * This method iterates through the list of entity names, constructs file paths, and registers + * entity variants using the {@link ReloadEventHandler}. + *

+ * + * @param pServer {@link MinecraftServer} - The server on which the entity variants will be loaded. + * @author MeAlam + * @since 1.0.0 + */ + public static void LoadEntityVariants(MinecraftServer pServer) { + for (String entityName : entityNames) { + String folderPath = basePath + entityName; + ReloadEventHandler.registerEntityVariants(folderPath, pServer, BlueLibConstants.MOD_ID, entityName); + BaseLogger.log(BaseLogLevel.INFO, "Entity variants loaded for " + entityName + ".", true); + } + } + + /** + * Registers the server start and reload event listeners. + * + * @author MeAlam + * @since 1.0.0 + */ + public static void registerEventListeners() { + ServerLifecycleEvents.SERVER_STARTING.register(ReloadHandler::onServerStart); + } +} diff --git a/fabric/src/main/java/software/bluelib/example/init/ClientInit.java b/fabric/src/main/java/software/bluelib/example/init/ClientInit.java new file mode 100644 index 00000000..b46334a3 --- /dev/null +++ b/fabric/src/main/java/software/bluelib/example/init/ClientInit.java @@ -0,0 +1,38 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.example.init; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.rendering.v1.EntityRendererRegistry; +import software.bluelib.BlueLibCommon; +import software.bluelib.BlueLibConstants; +import software.bluelib.example.entity.dragon.DragonRender; +import software.bluelib.example.entity.rex.RexRender; + +/** + * A {@code public class} that extends {@link ClientModInitializer} and contains the events that are fired on the client side. + *

+ * Key Methods: + *

    + *
  • {@link #onInitializeClient()} - Registers the renderers for the entities.
  • + *
+ * + * @author MeAlam + * @since 1.0.0 + */ +public class ClientInit implements ClientModInitializer { + + /** + * A {@code public void} that registers the renderers for the entities. + * + * @author MeAlam + * @since 1.0.0 + */ + @Override + public void onInitializeClient() { + if (BlueLibCommon.isDeveloperMode() && BlueLibCommon.PLATFORM.isModLoaded("geckolib") && BlueLibConstants.isExampleEnabled) { + EntityRendererRegistry.register(ModEntities.EXAMPLE_ONE, DragonRender::new); + EntityRendererRegistry.register(ModEntities.EXAMPLE_TWO, RexRender::new); + } + } +} diff --git a/fabric/src/main/java/software/bluelib/example/init/ModEntities.java b/fabric/src/main/java/software/bluelib/example/init/ModEntities.java new file mode 100644 index 00000000..815cdd40 --- /dev/null +++ b/fabric/src/main/java/software/bluelib/example/init/ModEntities.java @@ -0,0 +1,59 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.example.init; + +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.EntityType; +import software.bluelib.BlueLibConstants; +import software.bluelib.example.entity.dragon.DragonEntity; +import software.bluelib.example.entity.rex.RexEntity; + +import static net.minecraft.world.entity.MobCategory.CREATURE; + +/** + * A {@code public class} that contains the entities for the mod. + *

+ * Key Methods: + *

    + *
  • {@link #initializeEntities()} - Initializes the entities.
  • + *
+ * + * @author MeAlam + * @since 1.0.0 + */ +public class ModEntities { + + /** + * The {@code public static} field that stores Example One. + */ + public static EntityType EXAMPLE_ONE; + + /** + * The {@code public static} field that stores Example Two. + */ + public static EntityType EXAMPLE_TWO; + + /** + * A {@code public static void} that initializes the entities. + * + * @author MeAlam + * @since 1.0.0 + */ + public static void initializeEntities() { + EXAMPLE_ONE = Registry.register( + BuiltInRegistries.ENTITY_TYPE, + new ResourceLocation(BlueLibConstants.MOD_ID, "example_one"), + EntityType.Builder.of(DragonEntity::new, CREATURE) + .sized(0.6F, 1.8F) + .build("example_one")); + + EXAMPLE_TWO = Registry.register( + BuiltInRegistries.ENTITY_TYPE, + new ResourceLocation(BlueLibConstants.MOD_ID, "example_two"), + EntityType.Builder.of(RexEntity::new, CREATURE) + .sized(0.6F, 1.8F) + .build("example_two")); + } +} diff --git a/NeoForge/src/main/java/software/bluelib/interfaces/variant/IVariantEntity.java b/fabric/src/main/java/software/bluelib/interfaces/variant/IVariantEntity.java similarity index 59% rename from NeoForge/src/main/java/software/bluelib/interfaces/variant/IVariantEntity.java rename to fabric/src/main/java/software/bluelib/interfaces/variant/IVariantEntity.java index a9bf1919..40f83867 100644 --- a/NeoForge/src/main/java/software/bluelib/interfaces/variant/IVariantEntity.java +++ b/fabric/src/main/java/software/bluelib/interfaces/variant/IVariantEntity.java @@ -4,11 +4,13 @@ import net.minecraft.util.RandomSource; import software.bluelib.interfaces.variant.base.IVariantEntityBase; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; import java.util.List; /** - * An {@code Interface} representing an entity that supports multiple variants. + * A {@code public Interface} representing an entity that supports multiple variants. *

* This interface extends {@link IVariantEntityBase} to include methods specific to handling entity variants, including * random selection of variants. @@ -18,39 +20,40 @@ *

    *
  • {@link #getRandomVariant(List, String)} - Retrieves a random variant name from a provided list or defaults if the list is empty.
  • *
- *

+ * * @author MeAlam - * @Co-author Dan * @since 1.0.0 */ public interface IVariantEntity extends IVariantEntityBase { /** * A {@link RandomSource} instance used for generating random variants. - * @Co-author MeAlam, Dan + * * @since 1.0.0 */ RandomSource random = RandomSource.create(); /** - * A {@link String} that selects a random variant name from the provided list of variant names. + * A {@code default} {@link String} that selects a random variant name from the provided list of variant names. *

* This method uses the {@link RandomSource} to pick a random variant from the list. If the list is empty, the default * variant name is returned. *

* * @param pVariantNamesList {@link List} - A {@link List} of variant names available for the entity. - * @param pDefaultVariant {@link String} - The default variant name to return if {@code pVariantNamesList} is empty. + * @param pDefaultVariant {@link String} - The default variant name to return if {@code pVariantNamesList} is empty. * @return A random variant name from the list, or the default variant if the list is empty. * @author MeAlam - * @Co-author Dan * @since 1.0.0 */ default String getRandomVariant(List pVariantNamesList, String pDefaultVariant) { - List spawnableVariants = pVariantNamesList.stream().toList(); - if (!spawnableVariants.isEmpty()) { - return spawnableVariants.get(this.random.nextInt(spawnableVariants.size())); + if (pVariantNamesList.isEmpty()) { + BaseLogger.log(BaseLogLevel.INFO, "Variant names list is empty. Returning default variant: " + pDefaultVariant, true); + return pDefaultVariant; } - return pDefaultVariant; + int index = random.nextInt(pVariantNamesList.size()); + String selectedVariant = pVariantNamesList.get(index); + BaseLogger.log(BaseLogLevel.SUCCESS, "Selected random variant: " + selectedVariant + " from list of size: " + pVariantNamesList.size(), true); + return selectedVariant; } } diff --git a/NeoForge/src/main/java/software/bluelib/interfaces/variant/base/IVariantEntityBase.java b/fabric/src/main/java/software/bluelib/interfaces/variant/base/IVariantEntityBase.java similarity index 72% rename from NeoForge/src/main/java/software/bluelib/interfaces/variant/base/IVariantEntityBase.java rename to fabric/src/main/java/software/bluelib/interfaces/variant/base/IVariantEntityBase.java index 3b905d28..5019cf05 100644 --- a/NeoForge/src/main/java/software/bluelib/interfaces/variant/base/IVariantEntityBase.java +++ b/fabric/src/main/java/software/bluelib/interfaces/variant/base/IVariantEntityBase.java @@ -5,12 +5,14 @@ import net.minecraft.resources.ResourceLocation; import software.bluelib.entity.variant.VariantLoader; import software.bluelib.entity.variant.VariantParameter; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; import java.util.List; import java.util.stream.Collectors; /** - * A {@code base Interface} providing fundamental methods for handling entity variants. + * A {@code public base Interface} providing fundamental methods for handling entity variants. *

* This interface defines methods for retrieving texture locations and variant names associated with entities. *

@@ -20,24 +22,22 @@ *
  • {@link #getTextureLocation(String, String)} - Retrieves the {@link ResourceLocation} for the entity texture.
  • *
  • {@link #getEntityVariants(String)} - Retrieves a {@link List} of variant names for a specified entity.
  • * - *

    + * * @author MeAlam - * @Co-author Dan * @since 1.0.0 */ public interface IVariantEntityBase { /** - * A {@link ResourceLocation} that points to the texture of an entity. + * A {@code default} {@link ResourceLocation} that points to the texture of an entity. *

    * This method constructs a {@link ResourceLocation} using the provided mod ID and texture path. *

    * * @param pModId {@link String} - The mod ID used to locate the texture. - * @param pPath {@link String} - The path to the texture within the mod. + * @param pPath {@link String} - The path to the texture within the mod. * @return A {@link ResourceLocation} pointing to the specified texture. * @author MeAlam - * @Co-author Dan * @since 1.0.0 */ default ResourceLocation getTextureLocation(String pModId, String pPath) { @@ -45,7 +45,7 @@ default ResourceLocation getTextureLocation(String pModId, String pPath) { } /** - * A {@link List} of variant names associated with the specified entity. + * A {@code default} {@link List} of variant names associated with the specified entity. *

    * This method retrieves the names of all variants for a given entity by querying the {@link VariantLoader}. *

    @@ -53,13 +53,14 @@ default ResourceLocation getTextureLocation(String pModId, String pPath) { * @param pEntityName {@link String} - The name of the entity whose variant names are to be retrieved. * @return A {@link List} containing the names of variants associated with the specified entity. * @author MeAlam - * @Co-author Dan * @since 1.0.0 */ default List getEntityVariants(String pEntityName) { List variants = VariantLoader.getVariantsFromEntity(pEntityName); - return variants.stream() - .map(VariantParameter::getVariantName) + List variantNames = variants.stream() + .map(VariantParameter::getVariantParameter) .collect(Collectors.toList()); + BaseLogger.log(BaseLogLevel.SUCCESS, "Retrieved " + variantNames.size() + " variants for entity: " + pEntityName, true); + return variantNames; } } diff --git a/NeoForge/src/main/java/software/bluelib/json/JSONLoader.java b/fabric/src/main/java/software/bluelib/json/JSONLoader.java similarity index 55% rename from NeoForge/src/main/java/software/bluelib/json/JSONLoader.java rename to fabric/src/main/java/software/bluelib/json/JSONLoader.java index f046903c..6fa2d5b1 100644 --- a/NeoForge/src/main/java/software/bluelib/json/JSONLoader.java +++ b/fabric/src/main/java/software/bluelib/json/JSONLoader.java @@ -7,7 +7,8 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.resources.Resource; import net.minecraft.server.packs.resources.ResourceManager; -import software.bluelib.exception.CouldNotLoadJSON; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; import java.io.IOException; import java.io.InputStream; @@ -16,54 +17,57 @@ import java.util.Optional; /** - * The {@code JSONLoader} class is responsible for loading and parsing JSON data from + * A {@code public class} responsible for loading and parsing JSON data from * resources defined by {@link ResourceLocation} within a Minecraft mod environment.
    * It uses the {@link Gson} library to convert JSON strings into {@link JsonObject} instances. *

    - * Key methods: + * Key Methods: *

      - *
    • {@link #loadJson(ResourceLocation, ResourceManager)} - Loads a JSON resource.
    • + *
    • {@link #loadJson(ResourceLocation, ResourceManager)} - Loads a JSON resource from the specified location.
    • *
    + * * @author MeAlam - * @Co-author Dan * @since 1.0.0 */ public class JSONLoader { /** - * A {@link Gson} instance for parsing JSON data. - * @Co-author MeAlam, Dan + * A {@code private static} {@link Gson} instance for parsing JSON data. */ private static final Gson gson = new Gson(); /** - * A {@link JsonObject} that loads JSON data from a {@link ResourceLocation}.
    + * A {@code public} {@link JsonObject} that loads JSON data from a {@link ResourceLocation}.
    * This method is typically used to load configuration files or other JSON-based resources * in a Minecraft mod environment. - *

    + * * @param pResourceLocation {@link ResourceLocation} - The {@link ResourceLocation} of the JSON resource. - * @param pResourceManager {@link ResourceManager} - The {@link ResourceManager} to load the resource. - * @return The loaded {@link JsonObject}. - * @throws CouldNotLoadJSON If the JSON could not be loaded. + * @param pResourceManager {@link ResourceManager} - The {@link ResourceManager} used to load the resource. + * @return The loaded {@link JsonObject}. Returns an empty {@link JsonObject} if the resource is not found. + * @throws RuntimeException if there is an error reading the resource. * @author MeAlam - * @Co-author Dan * @since 1.0.0 */ - public JsonObject loadJson(ResourceLocation pResourceLocation, ResourceManager pResourceManager) throws CouldNotLoadJSON { + public JsonObject loadJson(ResourceLocation pResourceLocation, ResourceManager pResourceManager) { try { Optional resource = pResourceManager.getResource(pResourceLocation); if (resource.isEmpty()) { + BaseLogger.log(BaseLogLevel.ERROR, "Resource not found: " + pResourceLocation, true); return new JsonObject(); } try (InputStream inputStream = resource.get().open(); InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) { - return gson.fromJson(reader, JsonObject.class); + JsonObject jsonObject = gson.fromJson(reader, JsonObject.class); + BaseLogger.log(BaseLogLevel.SUCCESS, "Successfully loaded JSON resource: " + pResourceLocation, true); + return jsonObject; } } catch (IOException pException) { - throw new CouldNotLoadJSON("Failed to load JSON from resource: " + pResourceLocation + ". Error: " + pException.getMessage(), pResourceLocation.toString()); + RuntimeException exception = new RuntimeException("Failed to load JSON resource: " + pResourceLocation, pException); + BaseLogger.log(BaseLogLevel.ERROR, "Failed to load JSON resource: " + pResourceLocation, exception, true); + throw exception; } } } diff --git a/NeoForge/src/main/java/software/bluelib/json/JSONMerger.java b/fabric/src/main/java/software/bluelib/json/JSONMerger.java similarity index 68% rename from NeoForge/src/main/java/software/bluelib/json/JSONMerger.java rename to fabric/src/main/java/software/bluelib/json/JSONMerger.java index 918ed2cf..e82cf1ff 100644 --- a/NeoForge/src/main/java/software/bluelib/json/JSONMerger.java +++ b/fabric/src/main/java/software/bluelib/json/JSONMerger.java @@ -5,11 +5,13 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; import java.util.Map; /** - * A {@code Class} responsible for merging JSON data from a source {@link JsonObject} into a target {@link JsonObject}. + * A {@code public class} responsible for merging JSON data from a source {@link JsonObject} into a target {@link JsonObject}. *

    * This class provides functionality to combine JSON data where overlapping keys result in merging arrays, * and non-overlapping keys are simply added to the target. @@ -20,15 +22,14 @@ *

      *
    • {@link #mergeJsonObjects(JsonObject, JsonObject)} - Merges the data from the source JSON object into the target JSON object.
    • *
    - *

    + * * @author MeAlam - * @Co-author Dan * @since 1.0.0 */ public class JSONMerger { /** - * Merges data from a source {@link JsonObject} into a target {@link JsonObject}. + * A {@code public void} method that merges data from a source {@link JsonObject} into a target {@link JsonObject}. *

    * If the target JSON object already contains a key present in the source JSON object, the values are merged if they are arrays. * Otherwise, the source value is added to the target JSON object. @@ -36,15 +37,15 @@ public class JSONMerger { * * @param pTarget {@link JsonObject} - The target {@link JsonObject} to merge data into. This object will be modified by adding or updating its values. * @param pSource {@link JsonObject} - The source {@link JsonObject} to merge data from. This object is not modified by the operation. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 */ public void mergeJsonObjects(JsonObject pTarget, JsonObject pSource) { + for (Map.Entry entry : pSource.entrySet()) { - if (pTarget.has(entry.getKey())) { - JsonElement targetElement = pTarget.get(entry.getKey()); - JsonElement sourceElement = entry.getValue(); + String key = entry.getKey(); + JsonElement sourceElement = entry.getValue(); + + if (pTarget.has(key)) { + JsonElement targetElement = pTarget.get(key); if (targetElement.isJsonArray() && sourceElement.isJsonArray()) { JsonArray targetArray = targetElement.getAsJsonArray(); @@ -53,11 +54,15 @@ public void mergeJsonObjects(JsonObject pTarget, JsonObject pSource) { for (JsonElement element : sourceArray) { targetArray.add(element); } + + BaseLogger.log(BaseLogLevel.ERROR, "Merged array for key: " + key, true); } else { - pTarget.add(entry.getKey(), sourceElement); + pTarget.add(key, sourceElement); + BaseLogger.log(BaseLogLevel.WARNING, "Overwriting value for key: " + key, true); } } else { - pTarget.add(entry.getKey(), entry.getValue()); + pTarget.add(key, sourceElement); + BaseLogger.log(BaseLogLevel.SUCCESS, "Added new key: " + key, true); } } } diff --git a/fabric/src/main/java/software/bluelib/platform/FabricPlatformHelper.java b/fabric/src/main/java/software/bluelib/platform/FabricPlatformHelper.java new file mode 100644 index 00000000..a15e3a42 --- /dev/null +++ b/fabric/src/main/java/software/bluelib/platform/FabricPlatformHelper.java @@ -0,0 +1,65 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.platform; + +import net.fabricmc.loader.api.FabricLoader; +import software.bluelib.interfaces.platform.IPlatformHelper; + +/** + * A {@code public class} that provides platform-specific implementation for Fabric. + *

    + * This class implements {@link IPlatformHelper} to provide Fabric-specific functionality such as + * retrieving the platform name, checking if a mod is loaded, and determining if the game is running + * in a development environment. + *

    + *

    + * Key Methods: + *

      + *
    • {@link #getPlatformName()} - Returns the platform name for Fabric.
    • + *
    • {@link #isModLoaded(String)} - Checks if a mod is loaded using Fabric's mod loader.
    • + *
    • {@link #isDevelopmentEnvironment()} - Checks if Fabric is running in a development environment.
    • + *
    + * + * @author MeAlam + * @since 1.0.0 + */ +public class FabricPlatformHelper implements IPlatformHelper { + + /** + * A {@code public} {@link String} method that returns the name of the current platform, which is "Fabric" for this implementation. + * + * @return {@link String} - The platform name, "Fabric". + * @author MeAlam + * @since 1.0.0 + */ + @Override + public String getPlatformName() { + return "Fabric"; + } + + /** + * A {@code public} {@link Boolean} method that checks if a mod with the given ID is loaded using Fabric's mod loader. + * + * @param pModId {@link String} - The mod ID to check if it's loaded. + * @return {@code true} if the mod is loaded, {@code false} otherwise. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public boolean isModLoaded(String pModId) { + return FabricLoader.getInstance().isModLoaded(pModId); + } + + /** + * A {@code public} {@link Boolean} method that checks if the game is currently running in a development environment + * using Fabric's environment detection. + * + * @return {@code true} if running in a development environment, {@code false} if it isn't. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public boolean isDevelopmentEnvironment() { + return FabricLoader.getInstance().isDevelopmentEnvironment(); + } +} diff --git a/fabric/src/main/java/software/bluelib/utils/minecraft/ChunkUtils.java b/fabric/src/main/java/software/bluelib/utils/minecraft/ChunkUtils.java new file mode 100644 index 00000000..52704d81 --- /dev/null +++ b/fabric/src/main/java/software/bluelib/utils/minecraft/ChunkUtils.java @@ -0,0 +1,233 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.utils.minecraft; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.chunk.LevelChunk; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +import java.util.Arrays; +import java.util.Collection; +import java.util.stream.Collectors; + +/** + * A {@code class} providing methods to interact with Minecraft chunks, + * specifically for retrieving biome and tile entity information. + *

    + * Key Methods: + *

      + *
    • {@link #getBiomeOfChunk(Level, ChunkPos)} - Retrieves the {@link Biome} of the specified chunk.
    • + *
    • {@link #getBiomeRegistryNameOfChunk(Level, ChunkPos)} - Retrieves the biome registry name of the specified chunk.
    • + *
    • {@link #getBiomeSimpleNameOfChunk(Level, ChunkPos)} - Retrieves the simple name of the biome in the specified chunk.
    • + *
    • {@link #getChunkTileEntities(Level, ChunkPos)} - Retrieves the tile entities within the specified chunk.
    • + *
    • {@link #getChunkTileEntitiesRegistryNames(Level, ChunkPos)} - Retrieves the registry names of tile entities in the specified chunk.
    • + *
    • {@link #getChunkTileEntitiesSimpleNames(Level, ChunkPos)} - Retrieves the simple names of tile entities in the specified chunk.
    • + *
    • {@link #getChunkBlockCount(Level, ChunkPos)} - Counts the number of non-air blocks in the specified chunk.
    • + *
    + * + * @author MeAlam + * @since 1.0.0 + */ +public class ChunkUtils { + + /** + * Private constructor to prevent instantiation. + *

    + * This constructor is intentionally empty to prevent creating instances of this utility class. + *

    + * + * @author MeAlam + * @since 1.0.0 + */ + private ChunkUtils() { + } + + /** + * A {@link Biome} that retrieves the {@link Biome} of the specified chunk. + *

    + * Logs a success message if the biome is retrieved successfully, + * and an error message if an exception occurs. + *

    + * + * @param pLevel {@link Level} - The game world level. + * @param pChunkPos {@link ChunkPos} - The position of the chunk. + * @return The {@link Biome} associated with the specified chunk. + * @throws RuntimeException if there is an error retrieving the biome. + */ + public static Biome getBiomeOfChunk(Level pLevel, ChunkPos pChunkPos) { + try { + return pLevel.getBiome(pChunkPos.getWorldPosition()).value(); + } catch (Exception pException) { + BaseLogger.log(BaseLogLevel.ERROR, "Error retrieving biome for chunk at position " + pChunkPos, pException, true); + throw pException; + } + } + + + /** + * A {@link String} that retrieves the biome registry name of the specified chunk. + *

    + * Example: "minecraft:plains", "minecraft:desert" + *

    + * + * @param pLevel {@link Level} - The game world level. + * @param pChunkPos {@link ChunkPos} - The position of the chunk. + * @return The registry name of the chunk's biome as a {@link String}. + * @throws RuntimeException if there is an error retrieving the biome registry name. + */ + public static String getBiomeRegistryNameOfChunk(Level pLevel, ChunkPos pChunkPos) { + ResourceLocation biomeKey = pLevel.registryAccess() + .registryOrThrow(Registries.BIOME) + .getKey(pLevel.getBiome(pChunkPos.getWorldPosition()).value()); + + if (biomeKey == null) { + NullPointerException exception = new NullPointerException("Biome at chunk position " + pChunkPos + " is null"); + BaseLogger.log(BaseLogLevel.ERROR, "Error retrieving biome registry name of chunk at " + pChunkPos, exception, true); + return exception.getMessage(); + } + return biomeKey.toString(); + } + + + /** + * A {@link String} that retrieves the simple name of the biome in the specified chunk. + *

    + * Example: "plains", "desert" + *

    + * + * @param pLevel {@link Level} - The game world level. + * @param pChunkPos {@link ChunkPos} - The position of the chunk. + * @return The simple name of the chunk's biome. + */ + public static String getBiomeSimpleNameOfChunk(Level pLevel, ChunkPos pChunkPos) { + String registryName = getBiomeRegistryNameOfChunk(pLevel, pChunkPos); + return registryName.contains(":") ? registryName.split(":")[1] : registryName; + } + + /** + * A {@link Collection} that retrieves the tile entities within the specified chunk. + *

    + * Logs a success message with the number of tile entities retrieved, + * and an error message if an exception occurs. + *

    + * + * @param pLevel {@link Level} - The game world level. + * @param pChunkPos {@link ChunkPos} - The position of the chunk. + * @return A collection of tile entities present in the specified chunk. + * @throws RuntimeException if there is an error retrieving tile entities. + */ + public static Collection getChunkTileEntities(Level pLevel, ChunkPos pChunkPos) { + try { + LevelChunk chunk = pLevel.getChunk(pChunkPos.x, pChunkPos.z); + return chunk.getBlockEntities().values(); + } catch (Exception pException) { + BaseLogger.log(BaseLogLevel.ERROR, "Error retrieving tile entities for chunk at position " + pChunkPos, pException, true); + throw pException; + } + } + + + /** + * A {@link String} that retrieves the registry names of tile entities in the specified chunk. + *

    + * Example: "minecraft:chest, minecraft:furnace" + *

    + * + * @param pLevel {@link Level} - The game world level. + * @param pChunkPos {@link ChunkPos} - The position of the chunk. + * @return A comma-separated string of tile entity registry names in the chunk. + * @throws RuntimeException if there is an error retrieving tile entity registry names. + */ + public static String getChunkTileEntitiesRegistryNames(Level pLevel, ChunkPos pChunkPos) { + try { + Collection blockEntities = getChunkTileEntities(pLevel, pChunkPos); + + return blockEntities.stream() + .map(blockEntity -> { + ResourceLocation key = pLevel.registryAccess() + .registryOrThrow(Registries.BLOCK_ENTITY_TYPE) + .getKey(blockEntity.getType()); + + return key != null ? key.toString() : "unknown"; + }) + .collect(Collectors.joining(", ")); + } catch (Exception pException) { + BaseLogger.log(BaseLogLevel.ERROR, "Error retrieving tile entity registry names for chunk at position " + pChunkPos, pException, true); + throw pException; + } + } + + /** + * A {@link String} that retrieves the simple names of tile entities in the specified chunk. + *

    + * Example: "chest, furnace" + *

    + * + * @param pLevel {@link Level} - The game world level. + * @param pChunkPos {@link ChunkPos} - The position of the chunk. + * @return A comma-separated string of tile entity simple names in the chunk. + */ + public static String getChunkTileEntitiesSimpleNames(Level pLevel, ChunkPos pChunkPos) { + String registryNames = getChunkTileEntitiesRegistryNames(pLevel, pChunkPos); + + return Arrays.stream(registryNames.split(", ")) + .map(fullName -> fullName.contains(":") ? fullName.split(":")[1] : fullName) + .collect(Collectors.joining(", ")); + } + + /** + * A {@link Integer} that counts the number of non-air blocks in the specified chunk. + *

    + * Logs a success message with the block count, + * and an error message if an exception occurs. + *

    + * + * @param pLevel {@link Level} - The game world level. + * @param pChunkPos {@link ChunkPos} - The position of the chunk. + * @return The number of non-air blocks in the specified chunk. + * @throws RuntimeException if there is an error counting blocks. + */ + public static int getChunkBlockCount(Level pLevel, ChunkPos pChunkPos) { + try { + LevelChunk chunk = pLevel.getChunk(pChunkPos.x, pChunkPos.z); + int blockCount = 0; + + for (int x = 0; x < 16; x++) { + for (int y = pLevel.getMinBuildHeight(); y < pLevel.getHeight(); y++) { + for (int z = 0; z < 16; z++) { + BlockPos worldPos = new BlockPos(pChunkPos.getMinBlockX() + x, y, pChunkPos.getMinBlockZ() + z); + if (!chunk.getBlockState(worldPos).isAir()) { + blockCount++; + } + } + } + } + return blockCount; + } catch (Exception pException) { + BaseLogger.log(BaseLogLevel.ERROR, "Error counting blocks for chunk at position " + pChunkPos, pException, true); + throw pException; + } + } + + /** FIXME: This method is not working as expected. It is not returning correctly. + public static boolean isChunkLoaded(final LevelAccessor pWorld, final int pX, final int pZ) { + try { + boolean isLoaded = pWorld.getChunk(pX, pZ, ChunkStatus.FULL, false) != null; + BaseLogger.bluelibLogSuccess("Chunk at (" + pX + ", " + pZ + ") is loaded: " + isLoaded); + return isLoaded; + } catch (Exception e) { + BaseLogger.logError("Error checking if chunk at (" + pX + ", " + pZ + ") is loaded", e); + return false; + } + } + */ + + +} diff --git a/fabric/src/main/java/software/bluelib/utils/variant/ParameterUtils.java b/fabric/src/main/java/software/bluelib/utils/variant/ParameterUtils.java new file mode 100644 index 00000000..da316636 --- /dev/null +++ b/fabric/src/main/java/software/bluelib/utils/variant/ParameterUtils.java @@ -0,0 +1,184 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.utils.variant; + +import software.bluelib.entity.variant.VariantLoader; +import software.bluelib.entity.variant.VariantParameter; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; + +/** + * A utility class for managing custom parameters associated with entity variants. + *

    + * Provides methods to retrieve custom parameters for variants and allows for + * building and connecting parameters to specific variants via the {@link ParameterBuilder} class. + *

    + *

    + * Key Methods: + *

      + *
    • {@link #getParameter(String, String)} - Retrieves the value of a custom parameter for a specific variant.
    • + *
    + *

    + * Nested Classes: + *

      + *
    • {@link ParameterBuilder} - Builder class for creating and associating custom parameters with variants.
    • + *
    + * + * @author MeAlam + * @version 1.0.0 + * @see VariantParameter + * @since 1.0.0 + */ +public class ParameterUtils { + + /** + * Private constructor to prevent instantiation. + *

    + * This constructor is intentionally empty to prevent creating instances of this utility class. + *

    + * + * @author MeAlam + * @since 1.0.0 + */ + private ParameterUtils() { + } + + /** + * Holds custom parameters for each variant. + *

    + * The outer map's key is the variant name, and the inner map contains key-value pairs + * representing custom parameters for that variant. + *

    + * + * @since 1.0.0 + */ + private static final Map> variantParametersMap = new HashMap<>(); + + /** + * A {@link String} that retrieves the value of a custom parameter for a specific variant. + *

    + * If the parameter is not found, {@code null} is returned. + *

    + * + * @param pVariantName {@link String} The name of the variant. + * @param pParameterKey {@link String} The key of the parameter to retrieve. + * @return {@link String} The value of the custom parameter for the specified variant or {@code null} if not found. + * @author MeAlam + * @since 1.0.0 + */ + public static String getParameter(String pVariantName, String pParameterKey) { + return variantParametersMap.getOrDefault(pVariantName, new HashMap<>()).getOrDefault(pParameterKey, "null"); + } + + /** + * A {@code class} for creating and associating custom parameters with a specific variant. + *

    + * Allows chaining methods to build and connect parameters to a variant. + *

    + *

    + * Key Methods: + *

      + *
    • {@link #forVariant(String, String)} - Creates a new instance of {@link ParameterBuilder} for a specific entity and variant.
    • + *
    • {@link #withParameter(String)} - Adds a parameter with a default value of {@code null} .
    • + *
    • {@link #connect()} - Connects the parameters to the variant and updates {@link VariantParameter} with the parameters.
    • + *
    + * + * @author MeAlam + * @since 1.0.0 + */ + public static class ParameterBuilder { + + /** + * The name of the variant being associated with custom parameters. + * + * @since 1.0.0 + */ + private final String variantName; + + /** + * The name of the entity being associated with custom parameters. + * + * @since 1.0.0 + */ + private final String entityName; + + /** + * Stores custom parameters being built for the variant. + * + * @since 1.0.0 + */ + private final Map parameters = new HashMap<>(); + + /** + * Constructor to initialize the builder for a specific entity and variant. + * + * @param pEntityName {@link String} The name of the entity. + * @param pVariantName {@link String} The name of the variant. + * @author MeAlam + * @since 1.0.0 + */ + private ParameterBuilder(String pEntityName, String pVariantName) { + this.variantName = pVariantName; + this.entityName = pEntityName; + } + + /** + * A {@link ParameterBuilder} that creates a new instance of {@link ParameterBuilder} for the specified entity and variant. + * + * @param pEntityName {@link String} The name of the entity. + * @param pVariantName {@link String} The name of the variant. + * @return {@link ParameterBuilder} A new instance for chaining. + * @author MeAlam + * @since 1.0.0 + */ + public static ParameterBuilder forVariant(String pEntityName, String pVariantName) { + return new ParameterBuilder(pEntityName, pVariantName); + } + + /** + * A {@link ParameterBuilder} that adds a custom parameter to the builder with a default value of "null". + *

    + * The {@code null} value is used if the parameter is not specified in the data source. + *

    + * + * @param pParameter {@link String} The parameter key. + * @return {@link ParameterBuilder} The builder instance for chaining. + * @author MeAlam + * @since 1.0.0 + */ + public ParameterBuilder withParameter(String pParameter) { + parameters.put(pParameter, "null"); + return this; + } + + /** + * A {@link ParameterBuilder} that connects the custom parameters to the specified variant and updates the {@link VariantParameter}. + *

    + * Throws a {@link NoSuchElementException} if the variant or entity is not found. + *

    + * + * @return {@link ParameterBuilder} The builder instance for chaining. + * @throws NoSuchElementException if the variant or entity is not found in the database. + * @author MeAlam + * @since 1.0.0 + */ + public ParameterBuilder connect() { + VariantParameter variant = VariantLoader.getVariantByName(entityName, variantName); + if (variant != null) { + Map updatedParameters = new HashMap<>(); + for (String key : parameters.keySet()) { + updatedParameters.put(key, variant.getParameter(key)); + } + variantParametersMap.put(variantName, updatedParameters); + } else { + Throwable throwable = new Throwable("Variant or entity not found in the database"); + BaseLogger.log(BaseLogLevel.ERROR, "Variant '" + variantName + "' not found for entity '" + entityName + "'", throwable, true); + } + return this; + } + } +} diff --git a/fabric/src/main/resources/META-INF/services/software.bluelib.interfaces.platform.IPlatformHelper b/fabric/src/main/resources/META-INF/services/software.bluelib.interfaces.platform.IPlatformHelper new file mode 100644 index 00000000..5fdff29c --- /dev/null +++ b/fabric/src/main/resources/META-INF/services/software.bluelib.interfaces.platform.IPlatformHelper @@ -0,0 +1 @@ +software.bluelib.platform.FabricPlatformHelper \ No newline at end of file diff --git a/Forge/src/main/resources/assets/bluelib/animations/dragon.animation.json b/fabric/src/main/resources/assets/bluelib/animations/dragon.animation.json similarity index 100% rename from Forge/src/main/resources/assets/bluelib/animations/dragon.animation.json rename to fabric/src/main/resources/assets/bluelib/animations/dragon.animation.json diff --git a/Forge/src/main/resources/assets/bluelib/animations/rex.animation.json b/fabric/src/main/resources/assets/bluelib/animations/rex.animation.json similarity index 100% rename from Forge/src/main/resources/assets/bluelib/animations/rex.animation.json rename to fabric/src/main/resources/assets/bluelib/animations/rex.animation.json diff --git a/Forge/src/main/resources/assets/bluelib/geo/dragon.geo.json b/fabric/src/main/resources/assets/bluelib/geo/dragon.geo.json similarity index 100% rename from Forge/src/main/resources/assets/bluelib/geo/dragon.geo.json rename to fabric/src/main/resources/assets/bluelib/geo/dragon.geo.json diff --git a/Forge/src/main/resources/assets/bluelib/geo/rex.geo.json b/fabric/src/main/resources/assets/bluelib/geo/rex.geo.json similarity index 100% rename from Forge/src/main/resources/assets/bluelib/geo/rex.geo.json rename to fabric/src/main/resources/assets/bluelib/geo/rex.geo.json diff --git a/Forge/src/main/resources/assets/bluelib/lang/en_us.json b/fabric/src/main/resources/assets/bluelib/lang/en_us.json similarity index 100% rename from Forge/src/main/resources/assets/bluelib/lang/en_us.json rename to fabric/src/main/resources/assets/bluelib/lang/en_us.json diff --git a/fabric/src/main/resources/assets/bluelib/textures/entity/dragon/blue.png b/fabric/src/main/resources/assets/bluelib/textures/entity/dragon/blue.png new file mode 100644 index 0000000000000000000000000000000000000000..83be2147b8db3aaab415e12dc3b6e9a11a594ea4 GIT binary patch literal 676 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!7>k44ofvPP)Tsw@I14-?iy0WW zg+Z8+Vb&Z8pkQEtPlzj!7Pn|)P)=iz4q{MmaJHmZ`5KTT zT@vIM%(v z(aRpoCoWyiTgojaD&F|F!PdZVM@UbNjptVBj4jLF?f%O$LFQ-3hJ{yuCnpH{RqTF0 zeUaRrN`{moTZcM{69EVLnQmQO%s%OtOoW(UeeqPr*})2&$=17>Km8Ke@%eU3;;UAl z_dE-lA2QvTo9bV8@y{Lo#EEr$XX6_ew|<>1!7nqtJ8%c@3EiH9*VZm)@~qx!CojNR zaWk9uaEODlE8kU9hhhtcCz6N4bsR43<#29rQagW3>HO}AsXY7mmmhHEtgR04`g-ue zwv#SzoSD}|v`Lv6asM;6d&}sM&)V{9@7_H|f67<|ydtWYcW8+%(7BtgsyBPB&W+HL z{c<<=%Uw8|weJPP@nd2)_peY@i7iud+B+%Zp5z_{-H+UVy3Ca989GZ6MLiBbQ|UWb z(Y^fs4MF?eSt1usd41cW*!(Qw)s%f3B9+u@XC5>Aq_Xz;j^mF{Oz1waD#L%1(lgZ; z3|#E;@8(@%^IqToRn(#6{C~ELFSiAJm_Id{T)EuXZp3k!(UWyjbJC~xst$5b_e=bf q?_)BNT$9h7r)_M&3`GSo`xu+NS2^bO6ubn+JAk44ofvPP)Tsw@I14-?iy0WW zg+Z8+Vb&Z8pkQEtPlzj!Zjwt65OA~RHu2!IKd|xH`;UJotCY^rs5}_f`{vw>EiO$N z3LOzw<`r~)`(`)e=Yh7PEk`d-JKbu&a%s|urSjIR6`Z;x41ng`DxW(eV9(;TfP5fF zx+KUinBhN8a3r(H6zIsKo-U3d6?5KRy`8t%K!EMR{p2K$y_w2uzx|D07t}vHbT_ZS zqnACFPh7g3x0G8Pb3e8>o{E6%i-MMq;~$6()ryJQ+f9BFF)YSSz8_8_4VL` zZ6{scI5V$_Xp=HC;{IoB_m!M8-n(`vqUbO^7^(#vH4lVt10_7L@KG*&OB!JNoDQx9mgM^n9zM-Rfhj2rDv)y z7`WKw-_5(k=DoiEtEfZC`TuMgUv3NdFn?+?xpKL&-H78dqbKX6=A=*WRUPD>?w9x{ q-^XMkxh9`EPutjl8Hx&G_AxejuX4=mDR>EtcLq;aKbLh*2~7ZCIW7SJ literal 0 HcmV?d00001 diff --git a/Forge/src/main/resources/assets/bluelib/textures/entity/rex/brown.png b/fabric/src/main/resources/assets/bluelib/textures/entity/rex/brown.png similarity index 100% rename from Forge/src/main/resources/assets/bluelib/textures/entity/rex/brown.png rename to fabric/src/main/resources/assets/bluelib/textures/entity/rex/brown.png diff --git a/Forge/src/main/resources/assets/bluelib/textures/entity/rex/green.png b/fabric/src/main/resources/assets/bluelib/textures/entity/rex/green.png similarity index 100% rename from Forge/src/main/resources/assets/bluelib/textures/entity/rex/green.png rename to fabric/src/main/resources/assets/bluelib/textures/entity/rex/green.png diff --git a/fabric/src/main/resources/data/bluelib/variant/entity/dragon/blue.json b/fabric/src/main/resources/data/bluelib/variant/entity/dragon/blue.json new file mode 100644 index 00000000..1125138b --- /dev/null +++ b/fabric/src/main/resources/data/bluelib/variant/entity/dragon/blue.json @@ -0,0 +1,14 @@ +{ + "dragon": [ + { + "variantName": "blue", + "customParameter": "customValue3", + "int": 1, + "bool": true, + "array": [ + "boop", + "blep" + ] + } + ] +} \ No newline at end of file diff --git a/Forge/src/main/resources/data/bluelib/variant/entity/dragon.json b/fabric/src/main/resources/data/bluelib/variant/entity/dragon/dragon.json similarity index 100% rename from Forge/src/main/resources/data/bluelib/variant/entity/dragon.json rename to fabric/src/main/resources/data/bluelib/variant/entity/dragon/dragon.json diff --git a/fabric/src/main/resources/data/bluelib/variant/entity/dragon/pink.json b/fabric/src/main/resources/data/bluelib/variant/entity/dragon/pink.json new file mode 100644 index 00000000..4133639c --- /dev/null +++ b/fabric/src/main/resources/data/bluelib/variant/entity/dragon/pink.json @@ -0,0 +1,14 @@ +{ + "dragon": [ + { + "variantName": "pink", + "customParameter": "customValue3", + "int": 1, + "bool": true, + "array": [ + "boop", + "blep" + ] + } + ] +} \ No newline at end of file diff --git a/Forge/src/main/resources/data/bluelib/variant/entity/rex.json b/fabric/src/main/resources/data/bluelib/variant/entity/rex/rex.json similarity index 100% rename from Forge/src/main/resources/data/bluelib/variant/entity/rex.json rename to fabric/src/main/resources/data/bluelib/variant/entity/rex/rex.json diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json new file mode 100644 index 00000000..472d589c --- /dev/null +++ b/fabric/src/main/resources/fabric.mod.json @@ -0,0 +1,36 @@ +{ + "schemaVersion": 1, + "id": "${mod_id}", + "version": "${version}", + "name": "${mod_name}", + "description": "${description}", + "authors": [ + "${mod_author}" + ], + "contact": { + "homepage": "https://www.curseforge.com/minecraft/mc-mods/bluelib", + "issues": "https://github.com/MeAlam1/BlueLib/issues", + "sources": "https://github.com/MeAlam1/BlueLib" + }, + "license": "${license}", + "icon": "bluelib.png", + "environment": "*", + "entrypoints": { + "main": [ + "software.bluelib.BlueLib" + ], + "client": [ + "software.bluelib.example.init.ClientInit" + ] + }, + "depends": { + "fabricloader": ">=${fabric_loader_version}", + "fabric-api": "*", + "minecraft": "${minecraft_version}", + "java": ">=17" + }, + "suggests": { + "another-mod": "*" + } +} + \ No newline at end of file diff --git a/forge/build.gradle b/forge/build.gradle new file mode 100644 index 00000000..07152799 --- /dev/null +++ b/forge/build.gradle @@ -0,0 +1,123 @@ +plugins { + id 'idea' + id 'maven-publish' + id 'net.minecraftforge.gradle' version '[6.0,6.2)' + id 'org.spongepowered.mixin' version '0.7-SNAPSHOT' +} +base { + archivesName = "${mod_name}-forge-${minecraft_version}" +} + +repositories { + mavenCentral() + maven { + url "https://cursemaven.com" + } + mavenLocal() +} + +mixin { + add(sourceSets.main, "${mod_id}.refmap.json") + + //config("${mod_id}.mixins.json") + //config("${mod_id}.forge.mixins.json") +} + +minecraft { + mappings channel: 'official', version: minecraft_version + + copyIdeResources = true //Calls processResources when in dev + + // Automatically enable forge AccessTransformers if the file exists + // This location is hardcoded in Forge and can not be changed. + // https://github.com/MinecraftForge/MinecraftForge/blob/be1698bb1554f9c8fa2f58e32b9ab70bc4385e60/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ModFile.java#L123 + if (file('src/main/resources/META-INF/accesstransformer.cfg').exists()) { + accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') + } + + runs { + client { + workingDirectory project.file('run') + ideaModule "${rootProject.name}.${project.name}.main" + taskName 'Client' + property 'mixin.env.remapRefMap', 'true' + property 'mixin.env.refMapRemappingFile', "${projectDir}/build/createSrgToMcp/output.srg" + mods { + modClientRun { + source sourceSets.main + source project(":common").sourceSets.main + } + } + } + + server { + workingDirectory project.file('run') + ideaModule "${rootProject.name}.${project.name}.main" + taskName 'Server' + property 'mixin.env.remapRefMap', 'true' + property 'mixin.env.refMapRemappingFile', "${projectDir}/build/createSrgToMcp/output.srg" + mods { + modServerRun { + source sourceSets.main + source project(":common").sourceSets.main + } + } + } + + data { + workingDirectory project.file('run') + ideaModule "${rootProject.name}.${project.name}.main" + args '--mod', mod_id, '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources/') + taskName 'Data' + property 'mixin.env.remapRefMap', 'true' + property 'mixin.env.refMapRemappingFile', "${projectDir}/build/createSrgToMcp/output.srg" + mods { + modDataRun { + source sourceSets.main + source project(":common").sourceSets.main + } + } + } + } +} + +sourceSets.main.resources.srcDir 'src/generated/resources' + +dependencies { + minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}" + compileOnly project(":common") + annotationProcessor("org.spongepowered:mixin:0.8.5-SNAPSHOT:processor") + compileOnly fg.deobf("curse.maven:geckolib-388172:4933824") + runtimeOnly fg.deobf("curse.maven:geckolib-388172:4933824") +} + +tasks.withType(JavaCompile).configureEach { + source(project(":common").sourceSets.main.allSource) +} +tasks.withType(Javadoc).configureEach { + source(project(":common").sourceSets.main.allJava) +} +tasks.named("sourcesJar", Jar) { + from(project(":common").sourceSets.main.allSource) +} + +processResources { + from project(":common").sourceSets.main.resources +} + +jar.finalizedBy('reobfJar') + +publishing { + publications { + mavenJava(MavenPublication) { + artifactId base.archivesName.get() + from components.java + fg.component(it) + } + } + repositories { + maven { + url "file://" + System.getenv("local_maven") + } + } +} diff --git a/forge/src/main/java/software/bluelib/BlueLib.java b/forge/src/main/java/software/bluelib/BlueLib.java new file mode 100644 index 00000000..6d06a5d3 --- /dev/null +++ b/forge/src/main/java/software/bluelib/BlueLib.java @@ -0,0 +1,99 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib; + +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.DistExecutor; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; +import net.minecraftforge.fml.event.lifecycle.FMLLoadCompleteEvent; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import software.bluelib.example.event.ReloadHandler; +import software.bluelib.example.init.ModEntities; +import software.bluelib.example.proxy.ClientProxy; +import software.bluelib.example.proxy.CommonProxy; + +/** + * The main class of the {@link BlueLib} mod. + *

    + * This class serves as the entry point for the {@link BlueLib} mod, handling initialization by registering event handlers + * and setting up necessary configurations. For more details, refer to the BlueLib Wiki. + *

    + *

    + * Key Methods: + *

      + *
    • {@link #BlueLib()} - Constructs the {@link BlueLib} instance and registers the mod event bus.
    • + *
    • {@link #onLoadComplete(FMLLoadCompleteEvent)} - Handles the event when the mod loading is complete.
    • + *
    + * + * @author MeAlam + * @see BlueLib Wiki + * @since 1.0.0 + */ +@Mod(BlueLibConstants.MOD_ID) +public class BlueLib { + + /** + * A {@code public static} {@link CommonProxy} instance that handles the mod's initialization and event handling. + */ + public static CommonProxy PROXY = DistExecutor.safeRunForDist(() -> ClientProxy::new, () -> CommonProxy::new); + + /** + * Constructs a new {@link BlueLib} instance and registers the mod event bus. + * + * @author MeAlam + * @since 1.0.0 + */ + public BlueLib() { + IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus(); + modEventBus.register(this); + + if (BlueLibCommon.isDeveloperMode() && BlueLibCommon.PLATFORM.isModLoaded("geckolib") && BlueLibConstants.isExampleEnabled) { + ModEntities.register(modEventBus); + MinecraftForge.EVENT_BUS.register(ReloadHandler.class); + modEventBus.addListener(this::setupComplete); + modEventBus.addListener(this::setupClient); + } + } + + /** + * A {@code private void} that handles the {@link FMLClientSetupEvent}.
    + * Once the client setup process is complete, this method calls {@link ClientProxy#clientInit()} to perform additional client-side initialization. + * + * @param pEvent {@link FMLClientSetupEvent} - The event fired after the client setup process completes. + */ + private void setupClient(final FMLClientSetupEvent pEvent) { + pEvent.enqueueWork(() -> { + PROXY.clientInit(); + }); + } + + /** + * A {@code private void} that handles the {@link FMLLoadCompleteEvent}.
    + * Once the mod loading process is complete, this method calls {@link CommonProxy#postInit()} to perform additional initialization. + * + * @param pEvent {@link FMLLoadCompleteEvent} - The event fired after the mod loading process completes. + */ + private void setupComplete(final FMLLoadCompleteEvent pEvent) { + PROXY.postInit(); + } + + /** + * A {@code public void} method that handles the {@link FMLLoadCompleteEvent}, which is triggered once + * the mod loading process is complete. + *

    + * This method calls {@link BlueLibCommon#init()} to perform additional initialization after all mod-related + * loading steps are finished. + *

    + * + * @param pEvent {@link FMLLoadCompleteEvent} - The event fired after the mod loading process completes. + * @author MeAlam + * @since 1.0.0 + */ + @SubscribeEvent + public void onLoadComplete(FMLLoadCompleteEvent pEvent) { + BlueLibCommon.init(); + } +} diff --git a/forge/src/main/java/software/bluelib/entity/variant/VariantLoader.java b/forge/src/main/java/software/bluelib/entity/variant/VariantLoader.java new file mode 100644 index 00000000..f8257571 --- /dev/null +++ b/forge/src/main/java/software/bluelib/entity/variant/VariantLoader.java @@ -0,0 +1,189 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.entity.variant; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.packs.resources.ResourceManager; +import software.bluelib.interfaces.variant.base.IVariantEntityBase; +import software.bluelib.json.JSONLoader; +import software.bluelib.json.JSONMerger; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +import java.util.*; + +/** + * A {@code public class} that implements the {@link IVariantEntityBase} {@code interface} that manages the loading and storage of entity variants. + *

    + * The class handles loading and merging of JSON Data by utilizing the {@link JSONLoader} and {@link JSONMerger} classes.
    + * To load the Variants it loops through all resources in a folder and merges them into a single {@link JsonObject}.
    + * The merged JSON data is then parsed into {@link VariantParameter} instances and stored in {@link #entityVariantsMap}.
    + *

    + * Key Methods: + *
      + *
    • {@link #loadVariants(String, MinecraftServer, String)} - Loads and merges variant data by looping thru all resources in a folder.
    • + *
    • {@link #getVariantsFromEntity(String)} - Retrieves the list of loaded {@link VariantParameter} for a specific entity.
    • + *
    • {@link #getVariantByName(String, String)} - Retrieves a specific {@link VariantParameter} by its name for a given entity.
    • + *
    + * + * @author MeAlam + * @since 1.0.0 + */ +public class VariantLoader implements IVariantEntityBase { + + /** + * A {@code private static final} {@link Map} to store entity variants as key-value pairs. + *

    + * This {@link Map} holds entity names and their corresponding list of {@link VariantParameter} instances. + *

    + * + * @since 1.0.0 + */ + private static final Map> entityVariantsMap = new HashMap<>(); + + /** + * A {@code private static final} {@link JSONLoader} to load JSON data from resources. + * + * @since 1.0.0 + */ + private static final JSONLoader jsonLoader = new JSONLoader(); + + /** + * A {@code private static final} {@link JSONMerger} to merge JSON data. + *

    + * This {@link JSONMerger} instance is used to merge JSON data into a single {@link JsonObject}. + *

    + * + * @since 1.0.0 + */ + private static final JSONMerger jsonMerger = new JSONMerger(); + + /** + * A {@code public static void} that loads and merges variant data from JSON resources in the specified folder path. + *

    + * The method loops through all resources in the folder and merges them into a single {@link JsonObject}.
    + * The merged JSON data is then parsed into {@link VariantParameter} instances and stored in {@link #entityVariantsMap}. + *

    + * + * @param pFolderPath {@link String} - The path to the folder containing JSON resources. + * @param pServer {@link MinecraftServer} - The {@link MinecraftServer} instance used to access resources. + * @param pEntityName {@link String} - The name of the entity whose variants should be cleared before loading new ones. + */ + public static void loadVariants(String pFolderPath, MinecraftServer pServer, String pEntityName) { + + clearVariantsForEntity(pEntityName); + + ResourceManager resourceManager = pServer.getResourceManager(); + JsonObject mergedJsonObject = new JsonObject(); + + Collection collection = resourceManager.listResources(pFolderPath, pFiles -> pFiles.getPath().endsWith(".json")).keySet(); + + BaseLogger.log(BaseLogLevel.INFO, "Found resources: " + collection + " at: " + pFolderPath + " for: " + pEntityName, true); + + for (ResourceLocation resourceLocation : collection) { + try { + BaseLogger.log(BaseLogLevel.INFO, "Loading JSON data from resource: " + resourceLocation.toString(), true); + JsonObject jsonObject = jsonLoader.loadJson(resourceLocation, resourceManager); + jsonMerger.mergeJsonObjects(mergedJsonObject, jsonObject); + } catch (Exception pException) { + BaseLogger.log(BaseLogLevel.ERROR, "Failed to load JSON data from resource: " + resourceLocation.toString(), pException, true); + } + } + parseVariants(mergedJsonObject); + } + + /** + * A {@code private static void} that clears variants for a specific entity type from {@link #entityVariantsMap}. + *

    + * This method removes all variants associated with the given entity name. + *

    + * + * @param pEntityName {@link String} - The name of the entity whose variants should be cleared. + */ + private static void clearVariantsForEntity(String pEntityName) { + entityVariantsMap.remove(pEntityName); + } + + /** + * A {@code private static void} that parses the merged JSON data and converts it into {@link VariantParameter} instances. + *

    + * This method processes each entry in the JSON object and stores the created {@link VariantParameter} instances in {@link #entityVariantsMap}. + *

    + * + * @param pJsonObject {@link JsonObject} - The merged {@link JsonObject} containing variant data. + */ + private static void parseVariants(JsonObject pJsonObject) { + for (Map.Entry entry : pJsonObject.entrySet()) { + String entityName = entry.getKey(); + JsonArray textureArray = entry.getValue().getAsJsonArray(); + + BaseLogger.log(BaseLogLevel.INFO, "Parsing variants for entity: " + entityName, true); + List variantList = entityVariantsMap.computeIfAbsent(entityName, k -> new ArrayList<>()); + + for (JsonElement variant : textureArray) { + VariantParameter newVariant = getEntityVariant(entityName, variant.getAsJsonObject()); + + boolean variantExists = variantList.stream() + .anyMatch(v -> v.equals(newVariant)); + + if (!variantExists) { + variantList.add(newVariant); + } + } + } + } + + /** + * A {@code private static} {@link VariantParameter} that creates a new {@link VariantParameter} instance from a JSON object. + *

    + * This method wraps the creation of {@link VariantParameter} instances for easier management and potential modification. + *

    + * + * @param pJsonKey {@link String} - The key associated with this variant. + * @param pJsonObject {@link JsonObject} - The {@link JsonObject} containing the variant data. + * @return {@link VariantParameter} - A {@link VariantParameter} instance. + */ + private static VariantParameter getEntityVariant(String pJsonKey, JsonObject pJsonObject) { + return new VariantParameter(pJsonKey, pJsonObject); + } + + /** + * A {@code public static} {@link List} that retrieves the {@link List} of loaded {@link VariantParameter} instances for a specific entity. + *

    + * This method returns a list of variants for the given entity name. If no variants are found, an empty list is returned. + *

    + * + * @param pEntityName {@link String} - The name of the entity to retrieve variants for. + * @return {@link List} - A {@link List} of {@link VariantParameter} instances for the specified entity. + */ + public static List getVariantsFromEntity(String pEntityName) { + BaseLogger.log(BaseLogLevel.INFO, "Retrieving variants for entity: " + pEntityName, true); + return entityVariantsMap.getOrDefault(pEntityName, new ArrayList<>()); + } + + /** + * A {@code public static} {@link VariantParameter} that retrieves a {@link VariantParameter} for a specific entity, by the variant's name. + *

    + * This method searches for a variant with the specified name within the list of variants for the given entity. + *

    + * + * @param pEntityName {@link String} - The name of the entity to retrieve variants for. + * @param pVariantName {@link String} - The name of the variant to retrieve. + * @return {@link VariantParameter} - The {@link VariantParameter} with the specified name, or {@code null} if not found. + */ + public static VariantParameter getVariantByName(String pEntityName, String pVariantName) { + BaseLogger.log(BaseLogLevel.INFO, "Retrieving variant by name: " + pVariantName + " for entity: " + pEntityName, true); + List variants = getVariantsFromEntity(pEntityName); + for (VariantParameter variant : variants) { + if (variant.getVariantParameter().equals(pVariantName)) { + return variant; + } + } + BaseLogger.log(BaseLogLevel.INFO, "Variant with name: " + pVariantName + " not found for entity: " + pEntityName, true); + return null; + } +} diff --git a/forge/src/main/java/software/bluelib/entity/variant/VariantParameter.java b/forge/src/main/java/software/bluelib/entity/variant/VariantParameter.java new file mode 100644 index 00000000..a3805dba --- /dev/null +++ b/forge/src/main/java/software/bluelib/entity/variant/VariantParameter.java @@ -0,0 +1,174 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.entity.variant; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import software.bluelib.entity.variant.base.ParameterBase; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +import java.util.Map; +import java.util.Set; + +/** + * A {@code class} that represents the parameters associated with a specific variant of an entity. + *

    + * This class extends {@link ParameterBase} to store and manage variant-specific parameters parsed from a {@link JsonObject}. + *

    + * The class handles various JSON element types, including {@code JsonPrimitive}, {@code JsonArray}, and {@code JsonObject}. + *

    + * Key Methods: + *
      + *
    • {@link #getJsonKey()} - Retrieves the key of the JSON object that identifies this entity.
    • + *
    • {@link #getVariantParameter()} - Retrieves the name of the variant.
    • + *
    • {@link #getParameter(String)} - Retrieves the value of a specific parameter by its key.
    • + *
    + * + * @author MeAlam + * @since 1.0.0 + */ +public class VariantParameter extends ParameterBase { + + /** + * A {@code private final} {@link String} that represents the key of the JSON object that identifies this entity. + *

    + * This key is used to map the entity to its corresponding parameters within a {@link JsonObject}. + *

    + * + * @since 1.0.0 + */ + private final String jsonKey; + + /** + * A {@code private static} {@link String} that represents the name of the Variant parameter. + *

    + * This key is used to locate the variant name within the parameters/JSON files. + *

    + * + * @since 1.0.0 + */ + private static String variantParameterName = "variantName"; + + /** + * Constructs a new {@code VariantParameter} instance by extracting parameters from a given {@link JsonObject}. + *

    + * This constructor processes different types of {@link JsonElement} values: + *

      + *
    • {@link com.google.gson.JsonPrimitive}: Stored directly as a string.
    • + *
    • {@link com.google.gson.JsonArray}: Converts array elements into a single comma-separated string.
    • + *
    • {@link JsonObject}: Converts the nested JSON object to a string representation.
    • + *
    • {@code Other Types}: Stores "null" for unhandled JSON types.
    • + *
    + * + * @param pJsonKey {@link String} - The key that identifies this entity within the {@link JsonObject}. + * @param pJsonObject {@link JsonObject} - The {@link JsonObject} containing the variant parameters. + * @throws IllegalArgumentException if {@code pJsonKey} or {@code pJsonObject} is {@code null}. + * @author MeAlam + * @see ParameterBase + * @since 1.0.0 + */ + public VariantParameter(String pJsonKey, JsonObject pJsonObject) { + if (pJsonKey == null || pJsonObject == null) { + Throwable throwable = new Throwable("JSON key or JSON object is null"); + IllegalArgumentException exception = new IllegalArgumentException("JSON key and object must not be null"); + BaseLogger.log(BaseLogLevel.ERROR, exception.toString(), throwable, true); + throw exception; + } + this.jsonKey = pJsonKey; + BaseLogger.log(BaseLogLevel.INFO, "Creating VariantParameter with JSON key: " + pJsonKey, true); + Set> entryMap = pJsonObject.entrySet(); + for (Map.Entry entry : entryMap) { + JsonElement element = entry.getValue(); + if (element.isJsonPrimitive()) { + addParameter(entry.getKey(), element.getAsString()); + BaseLogger.log(BaseLogLevel.SUCCESS, "Added primitive parameter: " + entry.getKey() + " = " + element.getAsString(), true); + } else if (element.isJsonArray()) { + StringBuilder arrayValues = new StringBuilder(); + element.getAsJsonArray().forEach(e -> arrayValues.append(e.getAsString()).append(",")); + if (!arrayValues.isEmpty()) { + arrayValues.setLength(arrayValues.length() - 1); + } + addParameter(entry.getKey(), arrayValues.toString()); + BaseLogger.log(BaseLogLevel.SUCCESS, "Added array parameter: " + entry.getKey() + " = " + arrayValues, true); + } else if (element.isJsonObject()) { + addParameter(entry.getKey(), element.toString()); + BaseLogger.log(BaseLogLevel.SUCCESS, "Added object parameter: " + entry.getKey() + " = " + element, true); + } else { + addParameter(entry.getKey(), "null"); + BaseLogger.log(BaseLogLevel.SUCCESS, "Added null parameter for key: " + entry.getKey(), true); + } + } + } + + /** + * A {@link String} method that retrieves the key of the {@link JsonObject} that identifies this entity. + *

    + * This key is used to retrieve or map the entity within a broader data structure. + *

    + * + * @return The key of the JSON object representing this entity. + * @throws IllegalStateException if the key is unexpectedly {@code null}. + * @author MeAlam + * @since 1.0.0 + */ + public String getJsonKey() { + if (this.jsonKey == null) { + Throwable throwable = new Throwable("JSON key should not be null"); + IllegalStateException exception = new IllegalStateException("JSON key is unexpectedly null when retrieving from VariantParameter."); + BaseLogger.log(BaseLogLevel.ERROR, "JSON key is unexpectedly null when retrieving from VariantParameter.", throwable, true); + throw exception; + } + BaseLogger.log(BaseLogLevel.INFO, "Retrieved JSON key: " + this.jsonKey, true); + return this.jsonKey; + } + + /** + * A {@link String} method that retrieves the name of the variant parameter. + *

    + * The variant name is, by default, stored under the key {@code "variantName"} in the parameters/JSON files. + * Otherwise, the key is stored in the {@link #variantParameterName} field. + *

    + * + * @return The name of the variant, or {@code null} if the variant name is not found. + * @author MeAlam + * @since 1.0.0 + */ + public String getVariantParameter() { + String variantName = getParameter(variantParameterName); + BaseLogger.log(BaseLogLevel.INFO, "Retrieved parameter name: " + variantName, true); + return variantName; + } + + /** + * A {@link String} method that sets the name of the variant parameter. + *

    + * This method allows the user to customize the key used to locate the variant name within the parameters/JSON files. + *

    + * + * @param pCustomVariantName {@link String} - The custom name of the variant parameter. + * @author MeAlam + * @since 1.0.0 + */ + public void setVariantParameter(String pCustomVariantName) { + variantParameterName = pCustomVariantName; + BaseLogger.log(BaseLogLevel.INFO, "Setting parameter name: " + variantParameterName, true); + } + + /** + * A {@link String} method that retrieves the value of a specific parameter by its key. + *

    + * This method looks up the parameter's value within the internal data structure. + *

    + * + * @param pKey {@link String} - The key of the parameter to retrieve. + * @return The value of the parameter, or {@code null} if the key does not exist. + * @author MeAlam + * @since 1.0.0 + */ + public String getParameter(String pKey) { + String value = (String) super.getParameter(pKey); + BaseLogger.log(BaseLogLevel.INFO, "Retrieved parameter for key " + pKey + ": " + value, true); + return value; + } +} diff --git a/forge/src/main/java/software/bluelib/entity/variant/base/ParameterBase.java b/forge/src/main/java/software/bluelib/entity/variant/base/ParameterBase.java new file mode 100644 index 00000000..8e62626e --- /dev/null +++ b/forge/src/main/java/software/bluelib/entity/variant/base/ParameterBase.java @@ -0,0 +1,211 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.entity.variant.base; + +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * A {@code public abstract base class} for managing a collection of {@link #parameters}. + *

    + * This {@code class} provides methods to add, retrieve, remove, and manipulate {@link #parameters} stored as key-value pairs. + *

    + * Key Methods: + *
      + *
    • {@link #addParameter(String, Object)} - Adds a parameter to {@link #parameters}.
    • + *
    • {@link #getParameter(String)} - Retrieves a parameter from {@link #parameters}.
    • + *
    • {@link #removeParameter(String)} - Removes a parameter from {@link #parameters}.
    • + *
    • {@link #getAllParameters()} - Returns all parameters in {@link #parameters}.
    • + *
    • {@link #containsParameter(String)} - Checks if a parameter exists by its key from {@link #parameters}.
    • + *
    • {@link #isEmpty()} - Checks if {@link #parameters} is empty.
    • + *
    • {@link #clearParameters()} - Clears all parameters from {@link #parameters}.
    • + *
    • {@link #getParameterCount()} - Returns the number of parameters in {@link #parameters}.
    • + *
    • {@link #getParameterKeys()} - Returns a set of all parameter keys from {@link #parameters}.
    • + *
    • {@link #getParameterValues()} - Returns a collection of all parameter values from {@link #parameters}.
    • + *
    • {@link #updateParameter(String, Object)} - Updates the value of an existing parameter in {@link #parameters}.
    • + *
    + * + * @author MeAlam + * @since 1.0.0 + */ +public abstract class ParameterBase { + + /** + * A {@code private final} {@link Map} to store parameters as key-value pairs. + *

    + * This {@link Map} holds parameter keys and their corresponding values. + *

    + * + * @since 1.0.0 + */ + private final Map parameters = new HashMap<>(); + + /** + * A {@code protected void} that adds a parameter to {@link #parameters}. + *

    + * This method stores a new parameter with the specified key and value in {@link #parameters}. + *

    + * + * @param pKey {@link String} - The key under which the parameter is stored. + * @param pValue {@link Object} - The value of the parameter. + * @author MeAlam + * @since 1.0.0 + */ + protected void addParameter(String pKey, Object pValue) { + parameters.put(pKey, pValue); + } + + /** + * A {@code protected} {@link Object} that retrieves a parameter from {@link #parameters} by its key. + *

    + * This method returns the value associated with the specified key, or {@code null} if the key does not exist. + *

    + * + * @param pKey {@link String} - The key of the parameter to retrieve. + * @return {@link Object} - The value associated with the key, or {@code null} if the key does not exist. + * @author MeAlam + * @since 1.0.0 + */ + protected Object getParameter(String pKey) { + return parameters.get(pKey); + } + + /** + * A {@code protected void} that removes a parameter from {@link #parameters} by its key. + *

    + * This method deletes the parameter with the specified key from {@link #parameters}. If the key does not exist, no action is taken. + *

    + * + * @param pKey {@link String} - The key of the parameter to remove. + * @author MeAlam + * @since 1.0.0 + */ + protected void removeParameter(String pKey) { + if (parameters.remove(pKey) != null) { + BaseLogger.log(BaseLogLevel.SUCCESS, String.format("Parameter removed: Key = %s", pKey), true); + } else { + BaseLogger.log(BaseLogLevel.WARNING, String.format("Attempted to remove non-existent parameter: Key = %s", pKey), true); + } + } + + /** + * A {@code protected} {@link Map} that returns all parameters in {@link #parameters}. + *

    + * This method returns a new {@link Map} containing all parameters stored in {@link #parameters}. + *

    + * + * @return {@link Map} - A {@link Map} containing all parameters. + * @author MeAlam + * @since 1.0.0 + */ + protected Map getAllParameters() { + return new HashMap<>(parameters); + } + + /** + * A {@code protected} {@link Boolean} that checks if a parameter exists by its key. + *

    + * This method returns {@code true} if the parameter with the specified key exists in {@link #parameters}, {@code false} otherwise. + *

    + * + * @param pKey {@link String} - The key of the parameter to check. + * @return {@link Boolean} - {@code true} if the parameter exists and {@code false} if it doesn't. + * @author MeAlam + * @since 1.0.0 + */ + protected boolean containsParameter(String pKey) { + return parameters.containsKey(pKey); + } + + /** + * A {@code protected} {@link Boolean} that checks if {@link #parameters} is empty. + *

    + * This method returns {@code true} if {@link #parameters} contains no parameters, {@code false} otherwise. + *

    + * + * @return {@link Boolean} - {@code true} if {@link #parameters} is empty and {@code false} if it isn't. + * @author MeAlam + * @since 1.0.0 + */ + protected boolean isEmpty() { + return parameters.isEmpty(); + } + + /** + * A {@code protected void} that removes all parameters from {@link #parameters}. + * + * @author MeAlam + * @since 1.0.0 + */ + protected void clearParameters() { + parameters.clear(); + } + + /** + * A {@code protected} {@link Integer} that returns the number of parameters in {@link #parameters}. + * + * @return {@link Integer} - The number of parameters in the collection. + * @author MeAlam + * @since 1.0.0 + */ + protected int getParameterCount() { + return parameters.size(); + } + + /** + * A {@code protected} {@link Set} that returns a set of all parameter keys. + *

    + * This method provides a {@link Set} containing all the keys of parameters in {@link #parameters}. + *

    + * + * @return {@link Set} - A {@link Set} containing all parameter keys. + * @author MeAlam + * @since 1.0.0 + */ + protected Set getParameterKeys() { + return parameters.keySet(); + } + + /** + * A {@code protected} {@link Collection} that returns a {@link Collection} of all parameter values. + *

    + * This method provides a {@link Collection} containing all the values of parameters in {@link #parameters}. + *

    + * + * @return {@link Collection} - A {@link Collection} containing all parameter values. + * @author MeAlam + * @since 1.0.0 + */ + protected Collection getParameterValues() { + return parameters.values(); + } + + /** + * A {@code protected void} that updates the value of an existing parameter. + *

    + * This method changes the value of a parameter in {@link #parameters} that is identified by the specified key. If the key does not exist, an exception is thrown. + *

    + * + * @param pKey {@link String} - The key of the parameter to update. + * @param pNewValue {@link Object} - The new value to set for the parameter. + * @throws IllegalArgumentException if the key does not exist. + * @author MeAlam + * @since 1.0.0 + */ + protected void updateParameter(String pKey, Object pNewValue) { + if (parameters.containsKey(pKey)) { + parameters.put(pKey, pNewValue); + BaseLogger.log(BaseLogLevel.SUCCESS, String.format("Parameter updated: Key = %s, New Value = %s", pKey, pNewValue), true); + } else { + Throwable throwable = new Throwable("Key does not exist: " + pKey); + IllegalArgumentException exception = new IllegalArgumentException("Key does not exist: " + pKey); + BaseLogger.log(BaseLogLevel.ERROR, String.format("Attempted to update non-existent parameter: Key = %s", pKey), throwable, true); + throw exception; + } + } +} diff --git a/forge/src/main/java/software/bluelib/event/ReloadEventHandler.java b/forge/src/main/java/software/bluelib/event/ReloadEventHandler.java new file mode 100644 index 00000000..490c730b --- /dev/null +++ b/forge/src/main/java/software/bluelib/event/ReloadEventHandler.java @@ -0,0 +1,79 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.event; + +import com.google.gson.JsonParseException; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import software.bluelib.entity.variant.VariantLoader; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +/** + * A {@code class} responsible for handling events related to reloading entity variants. + *

    + * This class provides functionality to register entity variants from specified locations when the server starts. + *

    + *

    + * Key Features: + *

      + *
    • {@link #registerEntityVariants(String, MinecraftServer, String, String)} - Registers entity variants from specified locations.
    • + *
    + * + * @author MeAlam + * @see VariantLoader + * @see MinecraftServer + * @see ResourceLocation + * @since 1.0.0 + */ +public class ReloadEventHandler { + + /** + * A {@code protected static void} that registers entity variants from specified locations. + *

    + * This method attempts to load variants from both mod and datapack locations. It logs status information and + * handles exceptions that occur during the loading process. + *

    + *

    + * Parameters: + *

      + *
    • {@code pFolderPath} {@link String} - The folder path location within the mod or datapack where variants are stored.
    • + *
    • {@code pServer} {@link MinecraftServer} - The server instance of the current world.
    • + *
    • {@code pModID} {@link String} - The mod ID used to locate the entity variant resources. (Use your Mod's ID)
    • + *
    • {@code pEntityName} {@link String} - The entity name to load.
    • + *
    + * + * Exception Handling: + *
      + *
    • {@link JsonParseException} - Thrown when there is an error parsing the JSON files.
    • + *
    • {@link RuntimeException} - Thrown for unexpected errors during the registration process.
    • + *
    + * + * @param pFolderPath {@link String} - The folder path location within the mod or datapack where variants are stored. + * @param pServer {@link MinecraftServer} - The server instance of the current world. + * @param pModID {@link String} - The mod ID used to locate the entity variant resources. (Use your Mod's ID) + * @param pEntityName {@link String} - The entity name to load. + * @throws JsonParseException if there is an error parsing the JSON files. + * @throws RuntimeException if an unexpected error occurs during the registration process. + * @author MeAlam + * @see MinecraftServer + * @see ResourceLocation + * @see VariantLoader + * @since 1.0.0 + */ + protected static void registerEntityVariants(String pFolderPath, MinecraftServer pServer, String pModID, String pEntityName) { + + BaseLogger.log(BaseLogLevel.INFO, "Attempting to register entity variants for " + pEntityName + " with ModID: " + pModID, true); + + try { + VariantLoader.loadVariants(pFolderPath, pServer, pEntityName); + BaseLogger.log(BaseLogLevel.SUCCESS, "Successfully registered entity variants for " + pEntityName + " from ModID: " + pModID, true); + } catch (JsonParseException pException) { + BaseLogger.log(BaseLogLevel.ERROR, "Failed to parse JSON(s) while registering entity variants for " + pEntityName + " from ModID: " + pModID, pException, true); + throw pException; + } catch (Exception pException) { + BaseLogger.log(BaseLogLevel.ERROR, "Unexpected error occurred while registering entity variants for " + pEntityName + " from ModID: " + pModID, pException, true); + throw pException; + } + } +} diff --git a/Forge/src/main/java/software/bluelib/example/entity/dragon/DragonEntity.java b/forge/src/main/java/software/bluelib/example/entity/dragon/DragonEntity.java similarity index 77% rename from Forge/src/main/java/software/bluelib/example/entity/dragon/DragonEntity.java rename to forge/src/main/java/software/bluelib/example/entity/dragon/DragonEntity.java index 00e15560..413ca41a 100644 --- a/Forge/src/main/java/software/bluelib/example/entity/dragon/DragonEntity.java +++ b/forge/src/main/java/software/bluelib/example/entity/dragon/DragonEntity.java @@ -11,6 +11,7 @@ import net.minecraft.world.entity.*; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import net.minecraft.world.level.ServerLevelAccessor; import org.jetbrains.annotations.NotNull; @@ -20,7 +21,9 @@ import software.bernie.geckolib.core.animation.AnimatableManager; import software.bernie.geckolib.util.GeckoLibUtil; import software.bluelib.interfaces.variant.IVariantEntity; -import software.bluelib.utils.ParameterUtils; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; +import software.bluelib.utils.variant.ParameterUtils; /** * A {@code DragonEntity} class representing a dragon entity in the game, which extends {@link TamableAnimal} @@ -29,8 +32,6 @@ * This class manages the dragon's variant system, its data synchronization, and integrates with the GeckoLib * animation system. *

    - * - *

    * Key Methods: *

      *
    • {@link #defineSynchedData()} - Defines the synchronized data for the dragon entity, including its variant.
    • @@ -40,10 +41,8 @@ *
    • {@link #setVariantName(String)} - Sets the variant name of the dragon.
    • *
    • {@link #getVariantName()} - Retrieves the current variant name of the dragon.
    • *
    - *

    * * @author MeAlam - * @Co-author Dan * @since 1.0.0 */ public class DragonEntity extends TamableAnimal implements IVariantEntity, GeoEntity { @@ -52,14 +51,14 @@ public class DragonEntity extends TamableAnimal implements IVariantEntity, GeoEn *

    * This is used to store and retrieve the variant data for synchronization between server and client. *

    - * @Co-author MeAlam, Dan + * * @since 1.0.0 */ public static final EntityDataAccessor VARIANT = SynchedEntityData.defineId(DragonEntity.class, EntityDataSerializers.STRING); /** * The name of the entity. - * @Co-author MeAlam, Dan + * * @since 1.0.0 */ protected final String entityName = "dragon"; @@ -68,11 +67,9 @@ public class DragonEntity extends TamableAnimal implements IVariantEntity, GeoEn * Constructs a new {@link DragonEntity} instance with the specified entity type and level. * * @param pEntityType {@link EntityType} - The type of the entity. - * @param pLevel {@link Level} - The level in which the entity is created. - * - * @since 1.0.0 + * @param pLevel {@link Level} - The level in which the entity is created. * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ public DragonEntity(EntityType pEntityType, Level pLevel) { super(pEntityType, pLevel); @@ -84,9 +81,8 @@ public DragonEntity(EntityType pEntityType, Level pLeve * This method initializes the {@link EntityDataAccessor} to handle the variant data. *

    * - * @since 1.0.0 * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ @Override protected void defineSynchedData() { @@ -101,10 +97,8 @@ protected void defineSynchedData() { *

    * * @param pCompound {@link CompoundTag} - The NBT tag to which data should be added. - * - * @since 1.0.0 * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ @Override public void addAdditionalSaveData(@NotNull CompoundTag pCompound) { @@ -119,10 +113,8 @@ public void addAdditionalSaveData(@NotNull CompoundTag pCompound) { *

    * * @param pCompound {@link CompoundTag} - The NBT tag from which data should be read. - * - * @since 1.0.0 * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ @Override public void readAdditionalSaveData(@NotNull CompoundTag pCompound) { @@ -136,39 +128,35 @@ public void readAdditionalSaveData(@NotNull CompoundTag pCompound) { * This method sets up the variant for the entity and connects parameters if needed. *

    * - * @param pLevel {@link ServerLevelAccessor} - The level in which the entity is spawned. + * @param pLevel {@link ServerLevelAccessor} - The level in which the entity is spawned. * @param pDifficulty {@link DifficultyInstance} - The difficulty instance for spawning. - * @param pReason {@link MobSpawnType} - The reason for spawning the entity. - * @param pSpawnData {@link SpawnGroupData} - Data related to the spawn. - * @param pDataTag {@link CompoundTag} - Additional data for spawning. + * @param pReason {@link MobSpawnType} - The reason for spawning the entity. + * @param pSpawnData {@link SpawnGroupData} - Data related to the spawn. * @return {@link SpawnGroupData} - Updated spawn data. - * - * @since 1.0.0 * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ @Override - public SpawnGroupData finalizeSpawn(@NotNull ServerLevelAccessor pLevel, @NotNull DifficultyInstance pDifficulty, @NotNull MobSpawnType pReason, @Nullable SpawnGroupData pSpawnData, @Nullable CompoundTag pDataTag) { + public SpawnGroupData finalizeSpawn(@NotNull ServerLevelAccessor pLevel, @NotNull DifficultyInstance pDifficulty, @NotNull MobSpawnType pReason, @Nullable SpawnGroupData pSpawnData, @Nullable CompoundTag pSpawnTag) { if (getVariantName() == null || getVariantName().isEmpty()) { this.setVariantName(getRandomVariant(getEntityVariants(entityName), "normal")); - ParameterUtils.ParameterBuilder.forVariant(entityName,this.getVariantName()) + ParameterUtils.ParameterBuilder.forVariant(entityName, this.getVariantName()) .withParameter("customParameter") .withParameter("int") .withParameter("bool") .withParameter("array") .connect(); } - return super.finalizeSpawn(pLevel, pDifficulty, pReason, pSpawnData, pDataTag); + BaseLogger.log(BaseLogLevel.SUCCESS, "Dragon Spawned with Variant: " + getVariantName(), true); + return super.finalizeSpawn(pLevel, pDifficulty, pReason, pSpawnData, pSpawnTag); } /** * Sets the variant name for the dragon entity. * * @param pName {@link String} - The name of the variant to set. - * - * @since 1.0.0 * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ public void setVariantName(String pName) { this.entityData.set(VARIANT, pName); @@ -178,21 +166,28 @@ public void setVariantName(String pName) { * Retrieves the current variant name of the dragon entity. * * @return {@link String} - The current variant name. - * - * @since 1.0.0 * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ public String getVariantName() { return this.entityData.get(VARIANT); } + /* All Code below this Fragment is not Library Related!!! */ /** - * All Code below this Fragment is not Library Related!!! + * The cache for the animatable instance. + * + * @since 1.0.0 */ - private final AnimatableInstanceCache cache = GeckoLibUtil.createInstanceCache(this); + /** + * Defines the synchronized data for the dragon entity. + * + * @return {@link SynchedEntityData} - The builder for the synchronized data. + * @author MeAlam + * @since 1.0.0 + */ public static AttributeSupplier.Builder createAttributes() { return Mob.createMobAttributes() .add(Attributes.MOVEMENT_SPEED, 0.3) @@ -203,18 +198,54 @@ public static AttributeSupplier.Builder createAttributes() { .add(Attributes.FLYING_SPEED, 0.3); } + /** + * Adds custom data to the entity's NBT for saving. + * + * @param pControllerRegistrar {@link CompoundTag} - The tag to add the data to. + * @author MeAlam + * @since 1.0.0 + */ @Override public void registerControllers(AnimatableManager.ControllerRegistrar pControllerRegistrar) { } + /** + * Adds custom data to the entity's NBT for saving. + * + * @return {@link CompoundTag} - The tag with the custom data. + * @author MeAlam + * @since 1.0.0 + */ @Override public AnimatableInstanceCache getAnimatableInstanceCache() { return cache; } + /** + * Adds custom data to the entity's NBT for saving. + * + * @param pLevel {@link CompoundTag} - The tag to add the data to. + * @param pOtherParent {@link CompoundTag} - The other tag to add the data from. + * @return {@link CompoundTag} - The tag with the custom data. + * @author MeAlam + * @since 1.0.0 + */ @Nullable @Override public AgeableMob getBreedOffspring(@NotNull ServerLevel pLevel, @NotNull AgeableMob pOtherParent) { return null; } + + /** + * Adds custom data to the entity's NBT for saving. + * + * @param pItemStack {@link ItemStack} - The item stack to check. + * @return {@link boolean} - Whether the item is food or not. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public boolean isFood(@NotNull ItemStack pItemStack) { + return false; + } } diff --git a/forge/src/main/java/software/bluelib/example/entity/dragon/DragonModel.java b/forge/src/main/java/software/bluelib/example/entity/dragon/DragonModel.java new file mode 100644 index 00000000..d5f85626 --- /dev/null +++ b/forge/src/main/java/software/bluelib/example/entity/dragon/DragonModel.java @@ -0,0 +1,59 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.example.entity.dragon; + +import net.minecraft.resources.ResourceLocation; +import software.bernie.geckolib.model.GeoModel; +import software.bluelib.BlueLibConstants; + +/** + * A {@code public class} that extends {@link GeoModel} for the {@link DragonEntity} entity. + * Key Methods: + *
      + *
    • {@link #getModelResource(DragonEntity)} - Get the Model Location.
    • + *
    • {@link #getTextureResource(DragonEntity)} - Get the Texture Location.
    • + *
    • {@link #getAnimationResource(DragonEntity)} - Get the Animation Location.
    • + *
    + */ +public class DragonModel extends GeoModel { + + + /** + * A {@code public} {@link ResourceLocation} method that returns the location of the model. + * + * @param pObject {@link DragonEntity} - The entity to get the model for. + * @return {@link ResourceLocation} - The location of the model. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public ResourceLocation getModelResource(DragonEntity pObject) { + return new ResourceLocation(BlueLibConstants.MOD_ID, "geo/dragon.geo.json"); + } + + /** + * A {@code public} {@link ResourceLocation} method that returns the location of the texture. + * + * @param pObject {@link DragonEntity} - The entity to get the texture for. + * @return {@link ResourceLocation} - The location of the texture. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public ResourceLocation getTextureResource(DragonEntity pObject) { + return pObject.getTextureLocation(BlueLibConstants.MOD_ID, "textures/entity/" + pObject.entityName + "/" + pObject.getVariantName() + ".png"); + } + + /** + * A {@code public} {@link ResourceLocation} method that returns the location of the animation. + * + * @param pAnimatable {@link DragonEntity} - The entity to get the animation for. + * @return {@link ResourceLocation} - The location of the animation. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public ResourceLocation getAnimationResource(DragonEntity pAnimatable) { + return new ResourceLocation(BlueLibConstants.MOD_ID, "animations/dragon.animation.json"); + } +} diff --git a/Forge/src/main/java/software/bluelib/example/entity/dragon/DragonRender.java b/forge/src/main/java/software/bluelib/example/entity/dragon/DragonRender.java similarity index 57% rename from Forge/src/main/java/software/bluelib/example/entity/dragon/DragonRender.java rename to forge/src/main/java/software/bluelib/example/entity/dragon/DragonRender.java index c770ac48..d6a4e340 100644 --- a/Forge/src/main/java/software/bluelib/example/entity/dragon/DragonRender.java +++ b/forge/src/main/java/software/bluelib/example/entity/dragon/DragonRender.java @@ -5,9 +5,21 @@ import net.minecraft.client.renderer.entity.EntityRendererProvider; import software.bernie.geckolib.renderer.GeoEntityRenderer; +/** + * A {@code public class} that extends {@link GeoEntityRenderer} for rendering the dragon entity. + * + * @author MeAlam + * @since 1.0.0 + */ public class DragonRender extends GeoEntityRenderer { - // Render the entity + /** + * Constructor + * + * @param pRenderManager {@link EntityRendererProvider.Context} - The render manager. + * @author MeAlam + * @since 1.0.0 + */ public DragonRender(EntityRendererProvider.Context pRenderManager) { super(pRenderManager, new DragonModel()); } diff --git a/Forge/src/main/java/software/bluelib/example/entity/rex/RexEntity.java b/forge/src/main/java/software/bluelib/example/entity/rex/RexEntity.java similarity index 76% rename from Forge/src/main/java/software/bluelib/example/entity/rex/RexEntity.java rename to forge/src/main/java/software/bluelib/example/entity/rex/RexEntity.java index fcb85a52..270e9025 100644 --- a/Forge/src/main/java/software/bluelib/example/entity/rex/RexEntity.java +++ b/forge/src/main/java/software/bluelib/example/entity/rex/RexEntity.java @@ -11,6 +11,7 @@ import net.minecraft.world.entity.*; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import net.minecraft.world.level.ServerLevelAccessor; import org.jetbrains.annotations.NotNull; @@ -20,17 +21,17 @@ import software.bernie.geckolib.core.animation.AnimatableManager; import software.bernie.geckolib.util.GeckoLibUtil; import software.bluelib.interfaces.variant.IVariantEntity; -import software.bluelib.utils.ParameterUtils; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; +import software.bluelib.utils.variant.ParameterUtils; /** - * A {@code RexEntity} class representing a Rex entity in the game, which extends {@link TamableAnimal} + * A {@code rexEntity} class representing a rex entity in the game, which extends {@link TamableAnimal} * and implements {@link IVariantEntity} and {@link GeoEntity}. *

    * This class manages the rex's variant system, its data synchronization, and integrates with the GeckoLib * animation system. *

    - * - *

    * Key Methods: *

      *
    • {@link #defineSynchedData()} - Defines the synchronized data for the rex entity, including its variant.
    • @@ -40,10 +41,8 @@ *
    • {@link #setVariantName(String)} - Sets the variant name of the rex.
    • *
    • {@link #getVariantName()} - Retrieves the current variant name of the rex.
    • *
    - *

    * * @author MeAlam - * @Co-author Dan * @since 1.0.0 */ public class RexEntity extends TamableAnimal implements IVariantEntity, GeoEntity { @@ -52,14 +51,14 @@ public class RexEntity extends TamableAnimal implements IVariantEntity, GeoEntit *

    * This is used to store and retrieve the variant data for synchronization between server and client. *

    - * @Co-author MeAlam, Dan + * * @since 1.0.0 */ public static final EntityDataAccessor VARIANT = SynchedEntityData.defineId(RexEntity.class, EntityDataSerializers.STRING); /** * The name of the entity. - * @Co-author MeAlam, Dan + * * @since 1.0.0 */ protected final String entityName = "rex"; @@ -68,11 +67,9 @@ public class RexEntity extends TamableAnimal implements IVariantEntity, GeoEntit * Constructs a new {@link RexEntity} instance with the specified entity type and level. * * @param pEntityType {@link EntityType} - The type of the entity. - * @param pLevel {@link Level} - The level in which the entity is created. - * - * @since 1.0.0 + * @param pLevel {@link Level} - The level in which the entity is created. * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ public RexEntity(EntityType pEntityType, Level pLevel) { super(pEntityType, pLevel); @@ -84,9 +81,8 @@ public RexEntity(EntityType pEntityType, Level pLevel) * This method initializes the {@link EntityDataAccessor} to handle the variant data. *

    * - * @since 1.0.0 * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ @Override protected void defineSynchedData() { @@ -101,10 +97,8 @@ protected void defineSynchedData() { *

    * * @param pCompound {@link CompoundTag} - The NBT tag to which data should be added. - * - * @since 1.0.0 * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ @Override public void addAdditionalSaveData(@NotNull CompoundTag pCompound) { @@ -119,10 +113,8 @@ public void addAdditionalSaveData(@NotNull CompoundTag pCompound) { *

    * * @param pCompound {@link CompoundTag} - The NBT tag from which data should be read. - * - * @since 1.0.0 * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ @Override public void readAdditionalSaveData(@NotNull CompoundTag pCompound) { @@ -136,39 +128,35 @@ public void readAdditionalSaveData(@NotNull CompoundTag pCompound) { * This method sets up the variant for the entity and connects parameters if needed. *

    * - * @param pLevel {@link ServerLevelAccessor} - The level in which the entity is spawned. + * @param pLevel {@link ServerLevelAccessor} - The level in which the entity is spawned. * @param pDifficulty {@link DifficultyInstance} - The difficulty instance for spawning. - * @param pReason {@link MobSpawnType} - The reason for spawning the entity. - * @param pSpawnData {@link SpawnGroupData} - Data related to the spawn. - * @param pDataTag {@link CompoundTag} - Additional data for spawning. + * @param pReason {@link MobSpawnType} - The reason for spawning the entity. + * @param pSpawnData {@link SpawnGroupData} - Data related to the spawn. * @return {@link SpawnGroupData} - Updated spawn data. - * - * @since 1.0.0 * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ @Override - public SpawnGroupData finalizeSpawn(@NotNull ServerLevelAccessor pLevel, @NotNull DifficultyInstance pDifficulty, @NotNull MobSpawnType pReason, @Nullable SpawnGroupData pSpawnData, @Nullable CompoundTag pDataTag) { + public SpawnGroupData finalizeSpawn(@NotNull ServerLevelAccessor pLevel, @NotNull DifficultyInstance pDifficulty, @NotNull MobSpawnType pReason, @Nullable SpawnGroupData pSpawnData, @Nullable CompoundTag pSpawnTag) { if (getVariantName() == null || getVariantName().isEmpty()) { this.setVariantName(getRandomVariant(getEntityVariants(entityName), "normal")); - ParameterUtils.ParameterBuilder.forVariant(entityName,this.getVariantName()) + ParameterUtils.ParameterBuilder.forVariant(entityName, this.getVariantName()) .withParameter("customParameter") .withParameter("int") .withParameter("bool") .withParameter("array") .connect(); } - return super.finalizeSpawn(pLevel, pDifficulty, pReason, pSpawnData, pDataTag); + BaseLogger.log(BaseLogLevel.SUCCESS, "Rex Spawned with Variant: " + getVariantName(), true); + return super.finalizeSpawn(pLevel, pDifficulty, pReason, pSpawnData, pSpawnTag); } /** * Sets the variant name for the rex entity. * * @param pName {@link String} - The name of the variant to set. - * - * @since 1.0.0 * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ public void setVariantName(String pName) { this.entityData.set(VARIANT, pName); @@ -178,21 +166,28 @@ public void setVariantName(String pName) { * Retrieves the current variant name of the rex entity. * * @return {@link String} - The current variant name. - * - * @since 1.0.0 * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ public String getVariantName() { return this.entityData.get(VARIANT); } + /* All Code below this Fragment is not Library Related!!! */ /** - * All Code below this Fragment is not Library Related!!! + * The cache for the animatable instance. + * + * @since 1.0.0 */ - private final AnimatableInstanceCache cache = GeckoLibUtil.createInstanceCache(this); + /** + * Defines the synchronized data for the rex entity. + * + * @return {@link SynchedEntityData} - The builder for the synchronized data. + * @author MeAlam + * @since 1.0.0 + */ public static AttributeSupplier.Builder createAttributes() { return Mob.createMobAttributes() .add(Attributes.MOVEMENT_SPEED, 0.3) @@ -203,18 +198,54 @@ public static AttributeSupplier.Builder createAttributes() { .add(Attributes.FLYING_SPEED, 0.3); } + /** + * Adds custom data to the entity's NBT for saving. + * + * @param pControllerRegistrar {@link CompoundTag} - The tag to add the data to. + * @author MeAlam + * @since 1.0.0 + */ @Override public void registerControllers(AnimatableManager.ControllerRegistrar pControllerRegistrar) { } + /** + * Adds custom data to the entity's NBT for saving. + * + * @return {@link CompoundTag} - The tag with the custom data. + * @author MeAlam + * @since 1.0.0 + */ @Override public AnimatableInstanceCache getAnimatableInstanceCache() { return cache; } + /** + * Adds custom data to the entity's NBT for saving. + * + * @param pLevel {@link CompoundTag} - The tag to add the data to. + * @param pOtherParent {@link CompoundTag} - The other tag to add the data from. + * @return {@link CompoundTag} - The tag with the custom data. + * @author MeAlam + * @since 1.0.0 + */ @Nullable @Override public AgeableMob getBreedOffspring(@NotNull ServerLevel pLevel, @NotNull AgeableMob pOtherParent) { return null; } + + /** + * Adds custom data to the entity's NBT for saving. + * + * @param pItemStack {@link ItemStack} - The item stack to check. + * @return {@link boolean} - Whether the item is food or not. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public boolean isFood(@NotNull ItemStack pItemStack) { + return false; + } } diff --git a/forge/src/main/java/software/bluelib/example/entity/rex/RexModel.java b/forge/src/main/java/software/bluelib/example/entity/rex/RexModel.java new file mode 100644 index 00000000..f408313b --- /dev/null +++ b/forge/src/main/java/software/bluelib/example/entity/rex/RexModel.java @@ -0,0 +1,59 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.example.entity.rex; + +import net.minecraft.resources.ResourceLocation; +import software.bernie.geckolib.model.GeoModel; +import software.bluelib.BlueLibConstants; + +/** + * A {@code public class} that extends {@link GeoModel} for the {@link RexEntity} entity. + * Key Methods: + *
      + *
    • {@link #getModelResource(RexEntity)} - Get the Model Location.
    • + *
    • {@link #getTextureResource(RexEntity)} - Get the Texture Location.
    • + *
    • {@link #getAnimationResource(RexEntity)} - Get the Animation Location.
    • + *
    + */ +public class RexModel extends GeoModel { + + + /** + * A {@code public} {@link ResourceLocation} method that returns the location of the model. + * + * @param pObject {@link RexEntity} - The entity to get the model for. + * @return {@link ResourceLocation} - The location of the model. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public ResourceLocation getModelResource(RexEntity pObject) { + return new ResourceLocation(BlueLibConstants.MOD_ID, "geo/rex.geo.json"); + } + + /** + * A {@code public} {@link ResourceLocation} method that returns the location of the texture. + * + * @param pObject {@link RexEntity} - The entity to get the texture for. + * @return {@link ResourceLocation} - The location of the texture. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public ResourceLocation getTextureResource(RexEntity pObject) { + return pObject.getTextureLocation(BlueLibConstants.MOD_ID, "textures/entity/" + pObject.entityName + "/" + pObject.getVariantName() + ".png"); + } + + /** + * A {@code public} {@link ResourceLocation} method that returns the location of the animation. + * + * @param pAnimatable {@link RexEntity} - The entity to get the animation for. + * @return {@link ResourceLocation} - The location of the animation. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public ResourceLocation getAnimationResource(RexEntity pAnimatable) { + return new ResourceLocation(BlueLibConstants.MOD_ID, "animations/rex.animation.json"); + } +} diff --git a/Forge/src/main/java/software/bluelib/example/entity/rex/RexRender.java b/forge/src/main/java/software/bluelib/example/entity/rex/RexRender.java similarity index 57% rename from Forge/src/main/java/software/bluelib/example/entity/rex/RexRender.java rename to forge/src/main/java/software/bluelib/example/entity/rex/RexRender.java index 776c5920..2ca92fae 100644 --- a/Forge/src/main/java/software/bluelib/example/entity/rex/RexRender.java +++ b/forge/src/main/java/software/bluelib/example/entity/rex/RexRender.java @@ -5,9 +5,21 @@ import net.minecraft.client.renderer.entity.EntityRendererProvider; import software.bernie.geckolib.renderer.GeoEntityRenderer; +/** + * A {@code public class} that extends {@link GeoEntityRenderer} for rendering the rex entity. + * + * @author MeAlam + * @since 1.0.0 + */ public class RexRender extends GeoEntityRenderer { - // Render the entity + /** + * Constructor + * + * @param pRenderManager {@link EntityRendererProvider.Context} - The render manager. + * @author MeAlam + * @since 1.0.0 + */ public RexRender(EntityRendererProvider.Context pRenderManager) { super(pRenderManager, new RexModel()); } diff --git a/forge/src/main/java/software/bluelib/example/event/ClientEvents.java b/forge/src/main/java/software/bluelib/example/event/ClientEvents.java new file mode 100644 index 00000000..76e7f603 --- /dev/null +++ b/forge/src/main/java/software/bluelib/example/event/ClientEvents.java @@ -0,0 +1,40 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.example.event; + +import net.minecraft.client.renderer.entity.EntityRenderers; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.fml.common.Mod; +import software.bluelib.BlueLibConstants; +import software.bluelib.example.entity.dragon.DragonRender; +import software.bluelib.example.entity.rex.RexRender; +import software.bluelib.example.init.ModEntities; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +/** + * A {@code public class} that contains the events that are fired on the client side. + *

    + * Key Methods: + *

      + *
    • {@link #registerRenderers()} - Registers the renderers for the entities.
    • + *
    + * + * @author MeAlam + * @since 1.0.0 + */ +@Mod.EventBusSubscriber(modid = BlueLibConstants.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT) +public class ClientEvents { + + /** + * A {@code public static void} that registers the renderers for the entities. + * + * @author MeAlam + * @since 1.0.0 + */ + public static void registerRenderers() { + EntityRenderers.register(ModEntities.DRAGON.get(), DragonRender::new); + EntityRenderers.register(ModEntities.REX.get(), RexRender::new); + BaseLogger.log(BaseLogLevel.INFO, "Registered Renderers for Entities", true); + } +} diff --git a/forge/src/main/java/software/bluelib/example/event/CommonModEvent.java b/forge/src/main/java/software/bluelib/example/event/CommonModEvent.java new file mode 100644 index 00000000..c53aada8 --- /dev/null +++ b/forge/src/main/java/software/bluelib/example/event/CommonModEvent.java @@ -0,0 +1,42 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.example.event; + +import net.minecraftforge.event.entity.EntityAttributeCreationEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import software.bluelib.BlueLibConstants; +import software.bluelib.example.entity.dragon.DragonEntity; +import software.bluelib.example.entity.rex.RexEntity; +import software.bluelib.example.init.ModEntities; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +/** + * A {@code public class} that contains the events that are fired on the common side. + *

    + * Key Methods: + *

      + *
    • {@link #onAttributeCreate(EntityAttributeCreationEvent)} - Registers the attributes for the entities.
    • + *
    + * + * @author MeAlam + * @since 1.0.0 + */ +@Mod.EventBusSubscriber(modid = BlueLibConstants.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD) +public class CommonModEvent { + + /** + * A {@code public static void} that registers the attributes for the entities. + * + * @param pEvent {@link EntityAttributeCreationEvent} - The event that is fired when the attributes are being registered. + * @author MeAlam + * @since 1.0.0 + */ + @SubscribeEvent + public static void onAttributeCreate(EntityAttributeCreationEvent pEvent) { + pEvent.put(ModEntities.DRAGON.get(), DragonEntity.createAttributes().build()); + pEvent.put(ModEntities.REX.get(), RexEntity.createAttributes().build()); + BaseLogger.log(BaseLogLevel.INFO, "Registered Attributes for Entities", true); + } +} diff --git a/Forge/src/main/java/software/bluelib/example/event/ReloadHandler.java b/forge/src/main/java/software/bluelib/example/event/ReloadHandler.java similarity index 80% rename from Forge/src/main/java/software/bluelib/example/event/ReloadHandler.java rename to forge/src/main/java/software/bluelib/example/event/ReloadHandler.java index 24ed12b0..56fe6112 100644 --- a/Forge/src/main/java/software/bluelib/example/event/ReloadHandler.java +++ b/forge/src/main/java/software/bluelib/example/event/ReloadHandler.java @@ -6,14 +6,13 @@ import net.minecraftforge.event.AddReloadListenerEvent; import net.minecraftforge.event.server.ServerStartingEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.common.Mod; -import software.bluelib.BlueLib; +import software.bluelib.BlueLibConstants; import software.bluelib.event.ReloadEventHandler; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; import java.util.Arrays; import java.util.List; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** @@ -22,19 +21,15 @@ * This class extends {@link ReloadEventHandler} and implements event handling for server starting and reloading, * ensuring that entity variant data is properly loaded and refreshed. *

    - * - *

    * Key Methods: *

      *
    • {@link #onServerStart(ServerStartingEvent)} - Handles server starting events to initialize entity variants.
    • *
    • {@link #onReload(AddReloadListenerEvent)} - Handles reload events to refresh entity variants.
    • *
    • {@link #LoadEntityVariants(MinecraftServer)} - Loads entity variants from JSON files into the server.
    • *
    - *

    * - * @since 1.0.0 * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ public class ReloadHandler extends ReloadEventHandler { @@ -45,7 +40,6 @@ public class ReloadHandler extends ReloadEventHandler { *

    * * @since 1.0.0 - * @Co-author MeAlam, Dan */ private static MinecraftServer server; @@ -54,25 +48,16 @@ public class ReloadHandler extends ReloadEventHandler { * and load entity variants. * * @param pEvent {@link ServerStartingEvent} - The event triggered when the server starts. - * - * @since 1.0.0 * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ @SubscribeEvent public static void onServerStart(ServerStartingEvent pEvent) { server = pEvent.getServer(); ReloadHandler.LoadEntityVariants(server); + BaseLogger.log(BaseLogLevel.INFO, "Entity variants loaded.", true); } - /** - * The {@link ScheduledExecutorService} used to schedule tasks for reloading entity variants. - * - * @since 1.0.0 - * @Co-author MeAlam, Dan - */ - private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); - /** * Handles the reload event by scheduling a task to reload entity variants. *

    @@ -80,17 +65,16 @@ public static void onServerStart(ServerStartingEvent pEvent) { *

    * * @param pEvent {@link AddReloadListenerEvent} - The event triggered when a reload occurs. - * - * @since 1.0.0 * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ @SubscribeEvent public static void onReload(AddReloadListenerEvent pEvent) { if (server != null) { - scheduler.schedule(() -> { + BlueLibConstants.SCHEDULER.schedule(() -> { server.execute(() -> { ReloadHandler.LoadEntityVariants(server); + BaseLogger.log(BaseLogLevel.INFO, "Entity variants reloaded.", true); }); }, 1, TimeUnit.SECONDS); } @@ -103,7 +87,6 @@ public static void onReload(AddReloadListenerEvent pEvent) { *

    * * @since 1.0.0 - * @Co-author MeAlam, Dan */ private static final String basePath = "variant/entity/"; @@ -114,7 +97,6 @@ public static void onReload(AddReloadListenerEvent pEvent) { *

    * * @since 1.0.0 - * @Co-author MeAlam, Dan */ private static final List entityNames = Arrays.asList("dragon", "rex"); @@ -126,16 +108,14 @@ public static void onReload(AddReloadListenerEvent pEvent) { *

    * * @param pServer {@link MinecraftServer} - The server on which the entity variants will be loaded. - * - * @since 1.0.0 * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ public static void LoadEntityVariants(MinecraftServer pServer) { for (String entityName : entityNames) { - String modPath = basePath + entityName + ".json"; - String dataPath = basePath + entityName + "data.json"; - ReloadEventHandler.registerEntityVariants(pServer, entityName, BlueLib.MODID, modPath, dataPath); + String folderPath = basePath + entityName; + ReloadEventHandler.registerEntityVariants(folderPath, pServer, BlueLibConstants.MOD_ID, entityName); + BaseLogger.log(BaseLogLevel.INFO, "Entity variants loaded for " + entityName + ".", true); } } } diff --git a/Forge/src/main/java/software/bluelib/example/init/ModEntities.java b/forge/src/main/java/software/bluelib/example/init/ModEntities.java similarity index 54% rename from Forge/src/main/java/software/bluelib/example/init/ModEntities.java rename to forge/src/main/java/software/bluelib/example/init/ModEntities.java index 2825e2fe..d59247c5 100644 --- a/Forge/src/main/java/software/bluelib/example/init/ModEntities.java +++ b/forge/src/main/java/software/bluelib/example/init/ModEntities.java @@ -9,15 +9,33 @@ import net.minecraftforge.registries.DeferredRegister; import net.minecraftforge.registries.ForgeRegistries; import net.minecraftforge.registries.RegistryObject; -import software.bluelib.BlueLib; +import software.bluelib.BlueLibConstants; import software.bluelib.example.entity.dragon.DragonEntity; import software.bluelib.example.entity.rex.RexEntity; +/** + * A {@code public class} for registering {@link EntityType} for this mod. + *

    + * Key Methods: + *

      + *
    • {@link #register(IEventBus)} - Registers the {@link EntityType} for this mod.
    • + *
    + */ public class ModEntities { + + /** + * A {@code public static final} {@link DeferredRegister} of {@link EntityType} for this mod. + * + * @since 1.0.0 + */ public static final DeferredRegister> REGISTER = - DeferredRegister.create(ForgeRegistries.ENTITY_TYPES, BlueLib.MODID); + DeferredRegister.create(ForgeRegistries.ENTITY_TYPES, BlueLibConstants.MOD_ID); - // List of Entities + /** + * A {@code public static final} {@link RegistryObject} of {@link EntityType} for the {@link DragonEntity}. + * + * @since 1.0.0 + */ public static final RegistryObject> DRAGON = REGISTER.register("example_one", () -> EntityType.Builder.of(DragonEntity::new, MobCategory.AMBIENT) .setShouldReceiveVelocityUpdates(true) @@ -25,8 +43,13 @@ public class ModEntities { .setUpdateInterval(3) .fireImmune() .sized(0.6f, 1.8f) - .build(new ResourceLocation(BlueLib.MODID, "dragon").toString())); + .build(new ResourceLocation(BlueLibConstants.MOD_ID, "dragon").toString())); + /** + * A {@code public static final} {@link RegistryObject} of {@link EntityType} for the {@link RexEntity}. + * + * @since 1.0.0 + */ public static final RegistryObject> REX = REGISTER.register("example_two", () -> EntityType.Builder.of(RexEntity::new, MobCategory.AMBIENT) .setShouldReceiveVelocityUpdates(true) @@ -34,9 +57,16 @@ public class ModEntities { .setUpdateInterval(3) .fireImmune() .sized(0.6f, 1.8f) - .build(new ResourceLocation(BlueLib.MODID, "rex").toString())); + .build(new ResourceLocation(BlueLibConstants.MOD_ID, "rex").toString())); - public static void register(IEventBus eventBus) { - REGISTER.register(eventBus); + /** + * A {@code public static} method to register the {@link EntityType} for this mod. + * @param pEventBus {@link IEventBus} - The event bus to register the {@link EntityType} with. + * + * @author MeAlam + * @since 1.0.0 + */ + public static void register(IEventBus pEventBus) { + REGISTER.register(pEventBus); } } diff --git a/forge/src/main/java/software/bluelib/example/proxy/ClientProxy.java b/forge/src/main/java/software/bluelib/example/proxy/ClientProxy.java new file mode 100644 index 00000000..ce2612a5 --- /dev/null +++ b/forge/src/main/java/software/bluelib/example/proxy/ClientProxy.java @@ -0,0 +1,37 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.example.proxy; + +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.fml.common.Mod; +import software.bluelib.BlueLibConstants; +import software.bluelib.example.event.ClientEvents; + +/** + * A {@code public class} that extends {@link CommonProxy} and is annotated with {@link Mod.EventBusSubscriber} to handle + * client-side events. + *

    + * Key Methods: + *

      + *
    • {@link #clientInit()} - Handles the event after the initialization of the client.
    • + *
    + * + * @author MeAlam + * @see CommonProxy + * @since 1.0.0 + */ +@Mod.EventBusSubscriber(modid = BlueLibConstants.MOD_ID, value = Dist.CLIENT) +public class ClientProxy extends CommonProxy { + + /** + * A {@code public void} method that is called after the initialization of the client. + * + * @author MeAlam + * @since 1.0.0 + */ + @Override + public void clientInit() { + super.clientInit(); + ClientEvents.registerRenderers(); + } +} diff --git a/forge/src/main/java/software/bluelib/example/proxy/CommonProxy.java b/forge/src/main/java/software/bluelib/example/proxy/CommonProxy.java new file mode 100644 index 00000000..18692b69 --- /dev/null +++ b/forge/src/main/java/software/bluelib/example/proxy/CommonProxy.java @@ -0,0 +1,40 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.example.proxy; + +import net.minecraftforge.fml.common.Mod; +import software.bluelib.BlueLibConstants; + +/** + * A {@code public class} that is annotated with {@link Mod.EventBusSubscriber} to handle common events. + *

    + * Key Methods: + *

      + *
    • {@link #postInit()} - Handles the event after the initialization of the mod.
    • + *
    • {@link #clientInit()} - Handles the event after the initialization of the client.
    • + *
    + * + * @author MeAlam + * @since 1.0.0 + */ +@Mod.EventBusSubscriber(modid = BlueLibConstants.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD) +public class CommonProxy { + + /** + * A {@code public void} method that is called after the initialization of the mod. + * + * @author MeAlam + * @since 1.0.0 + */ + public void postInit() { + } + + /** + * A {@code public void} method that is called after the initialization of the client. + * + * @author MeAlam + * @since 1.0.0 + */ + public void clientInit() { + } +} diff --git a/forge/src/main/java/software/bluelib/interfaces/variant/IVariantEntity.java b/forge/src/main/java/software/bluelib/interfaces/variant/IVariantEntity.java new file mode 100644 index 00000000..40f83867 --- /dev/null +++ b/forge/src/main/java/software/bluelib/interfaces/variant/IVariantEntity.java @@ -0,0 +1,59 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.interfaces.variant; + +import net.minecraft.util.RandomSource; +import software.bluelib.interfaces.variant.base.IVariantEntityBase; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +import java.util.List; + +/** + * A {@code public Interface} representing an entity that supports multiple variants. + *

    + * This interface extends {@link IVariantEntityBase} to include methods specific to handling entity variants, including + * random selection of variants. + *

    + *

    + * Key Methods: + *

      + *
    • {@link #getRandomVariant(List, String)} - Retrieves a random variant name from a provided list or defaults if the list is empty.
    • + *
    + * + * @author MeAlam + * @since 1.0.0 + */ +public interface IVariantEntity extends IVariantEntityBase { + + /** + * A {@link RandomSource} instance used for generating random variants. + * + * @since 1.0.0 + */ + RandomSource random = RandomSource.create(); + + /** + * A {@code default} {@link String} that selects a random variant name from the provided list of variant names. + *

    + * This method uses the {@link RandomSource} to pick a random variant from the list. If the list is empty, the default + * variant name is returned. + *

    + * + * @param pVariantNamesList {@link List} - A {@link List} of variant names available for the entity. + * @param pDefaultVariant {@link String} - The default variant name to return if {@code pVariantNamesList} is empty. + * @return A random variant name from the list, or the default variant if the list is empty. + * @author MeAlam + * @since 1.0.0 + */ + default String getRandomVariant(List pVariantNamesList, String pDefaultVariant) { + if (pVariantNamesList.isEmpty()) { + BaseLogger.log(BaseLogLevel.INFO, "Variant names list is empty. Returning default variant: " + pDefaultVariant, true); + return pDefaultVariant; + } + int index = random.nextInt(pVariantNamesList.size()); + String selectedVariant = pVariantNamesList.get(index); + BaseLogger.log(BaseLogLevel.SUCCESS, "Selected random variant: " + selectedVariant + " from list of size: " + pVariantNamesList.size(), true); + return selectedVariant; + } +} diff --git a/Forge/src/main/java/software/bluelib/interfaces/variant/base/IVariantEntityBase.java b/forge/src/main/java/software/bluelib/interfaces/variant/base/IVariantEntityBase.java similarity index 72% rename from Forge/src/main/java/software/bluelib/interfaces/variant/base/IVariantEntityBase.java rename to forge/src/main/java/software/bluelib/interfaces/variant/base/IVariantEntityBase.java index 3b905d28..5019cf05 100644 --- a/Forge/src/main/java/software/bluelib/interfaces/variant/base/IVariantEntityBase.java +++ b/forge/src/main/java/software/bluelib/interfaces/variant/base/IVariantEntityBase.java @@ -5,12 +5,14 @@ import net.minecraft.resources.ResourceLocation; import software.bluelib.entity.variant.VariantLoader; import software.bluelib.entity.variant.VariantParameter; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; import java.util.List; import java.util.stream.Collectors; /** - * A {@code base Interface} providing fundamental methods for handling entity variants. + * A {@code public base Interface} providing fundamental methods for handling entity variants. *

    * This interface defines methods for retrieving texture locations and variant names associated with entities. *

    @@ -20,24 +22,22 @@ *
  • {@link #getTextureLocation(String, String)} - Retrieves the {@link ResourceLocation} for the entity texture.
  • *
  • {@link #getEntityVariants(String)} - Retrieves a {@link List} of variant names for a specified entity.
  • * - *

    + * * @author MeAlam - * @Co-author Dan * @since 1.0.0 */ public interface IVariantEntityBase { /** - * A {@link ResourceLocation} that points to the texture of an entity. + * A {@code default} {@link ResourceLocation} that points to the texture of an entity. *

    * This method constructs a {@link ResourceLocation} using the provided mod ID and texture path. *

    * * @param pModId {@link String} - The mod ID used to locate the texture. - * @param pPath {@link String} - The path to the texture within the mod. + * @param pPath {@link String} - The path to the texture within the mod. * @return A {@link ResourceLocation} pointing to the specified texture. * @author MeAlam - * @Co-author Dan * @since 1.0.0 */ default ResourceLocation getTextureLocation(String pModId, String pPath) { @@ -45,7 +45,7 @@ default ResourceLocation getTextureLocation(String pModId, String pPath) { } /** - * A {@link List} of variant names associated with the specified entity. + * A {@code default} {@link List} of variant names associated with the specified entity. *

    * This method retrieves the names of all variants for a given entity by querying the {@link VariantLoader}. *

    @@ -53,13 +53,14 @@ default ResourceLocation getTextureLocation(String pModId, String pPath) { * @param pEntityName {@link String} - The name of the entity whose variant names are to be retrieved. * @return A {@link List} containing the names of variants associated with the specified entity. * @author MeAlam - * @Co-author Dan * @since 1.0.0 */ default List getEntityVariants(String pEntityName) { List variants = VariantLoader.getVariantsFromEntity(pEntityName); - return variants.stream() - .map(VariantParameter::getVariantName) + List variantNames = variants.stream() + .map(VariantParameter::getVariantParameter) .collect(Collectors.toList()); + BaseLogger.log(BaseLogLevel.SUCCESS, "Retrieved " + variantNames.size() + " variants for entity: " + pEntityName, true); + return variantNames; } } diff --git a/Forge/src/main/java/software/bluelib/json/JSONLoader.java b/forge/src/main/java/software/bluelib/json/JSONLoader.java similarity index 55% rename from Forge/src/main/java/software/bluelib/json/JSONLoader.java rename to forge/src/main/java/software/bluelib/json/JSONLoader.java index f046903c..6fa2d5b1 100644 --- a/Forge/src/main/java/software/bluelib/json/JSONLoader.java +++ b/forge/src/main/java/software/bluelib/json/JSONLoader.java @@ -7,7 +7,8 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.resources.Resource; import net.minecraft.server.packs.resources.ResourceManager; -import software.bluelib.exception.CouldNotLoadJSON; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; import java.io.IOException; import java.io.InputStream; @@ -16,54 +17,57 @@ import java.util.Optional; /** - * The {@code JSONLoader} class is responsible for loading and parsing JSON data from + * A {@code public class} responsible for loading and parsing JSON data from * resources defined by {@link ResourceLocation} within a Minecraft mod environment.
    * It uses the {@link Gson} library to convert JSON strings into {@link JsonObject} instances. *

    - * Key methods: + * Key Methods: *

      - *
    • {@link #loadJson(ResourceLocation, ResourceManager)} - Loads a JSON resource.
    • + *
    • {@link #loadJson(ResourceLocation, ResourceManager)} - Loads a JSON resource from the specified location.
    • *
    + * * @author MeAlam - * @Co-author Dan * @since 1.0.0 */ public class JSONLoader { /** - * A {@link Gson} instance for parsing JSON data. - * @Co-author MeAlam, Dan + * A {@code private static} {@link Gson} instance for parsing JSON data. */ private static final Gson gson = new Gson(); /** - * A {@link JsonObject} that loads JSON data from a {@link ResourceLocation}.
    + * A {@code public} {@link JsonObject} that loads JSON data from a {@link ResourceLocation}.
    * This method is typically used to load configuration files or other JSON-based resources * in a Minecraft mod environment. - *

    + * * @param pResourceLocation {@link ResourceLocation} - The {@link ResourceLocation} of the JSON resource. - * @param pResourceManager {@link ResourceManager} - The {@link ResourceManager} to load the resource. - * @return The loaded {@link JsonObject}. - * @throws CouldNotLoadJSON If the JSON could not be loaded. + * @param pResourceManager {@link ResourceManager} - The {@link ResourceManager} used to load the resource. + * @return The loaded {@link JsonObject}. Returns an empty {@link JsonObject} if the resource is not found. + * @throws RuntimeException if there is an error reading the resource. * @author MeAlam - * @Co-author Dan * @since 1.0.0 */ - public JsonObject loadJson(ResourceLocation pResourceLocation, ResourceManager pResourceManager) throws CouldNotLoadJSON { + public JsonObject loadJson(ResourceLocation pResourceLocation, ResourceManager pResourceManager) { try { Optional resource = pResourceManager.getResource(pResourceLocation); if (resource.isEmpty()) { + BaseLogger.log(BaseLogLevel.ERROR, "Resource not found: " + pResourceLocation, true); return new JsonObject(); } try (InputStream inputStream = resource.get().open(); InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) { - return gson.fromJson(reader, JsonObject.class); + JsonObject jsonObject = gson.fromJson(reader, JsonObject.class); + BaseLogger.log(BaseLogLevel.SUCCESS, "Successfully loaded JSON resource: " + pResourceLocation, true); + return jsonObject; } } catch (IOException pException) { - throw new CouldNotLoadJSON("Failed to load JSON from resource: " + pResourceLocation + ". Error: " + pException.getMessage(), pResourceLocation.toString()); + RuntimeException exception = new RuntimeException("Failed to load JSON resource: " + pResourceLocation, pException); + BaseLogger.log(BaseLogLevel.ERROR, "Failed to load JSON resource: " + pResourceLocation, exception, true); + throw exception; } } } diff --git a/Forge/src/main/java/software/bluelib/json/JSONMerger.java b/forge/src/main/java/software/bluelib/json/JSONMerger.java similarity index 68% rename from Forge/src/main/java/software/bluelib/json/JSONMerger.java rename to forge/src/main/java/software/bluelib/json/JSONMerger.java index 918ed2cf..e82cf1ff 100644 --- a/Forge/src/main/java/software/bluelib/json/JSONMerger.java +++ b/forge/src/main/java/software/bluelib/json/JSONMerger.java @@ -5,11 +5,13 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; import java.util.Map; /** - * A {@code Class} responsible for merging JSON data from a source {@link JsonObject} into a target {@link JsonObject}. + * A {@code public class} responsible for merging JSON data from a source {@link JsonObject} into a target {@link JsonObject}. *

    * This class provides functionality to combine JSON data where overlapping keys result in merging arrays, * and non-overlapping keys are simply added to the target. @@ -20,15 +22,14 @@ *

      *
    • {@link #mergeJsonObjects(JsonObject, JsonObject)} - Merges the data from the source JSON object into the target JSON object.
    • *
    - *

    + * * @author MeAlam - * @Co-author Dan * @since 1.0.0 */ public class JSONMerger { /** - * Merges data from a source {@link JsonObject} into a target {@link JsonObject}. + * A {@code public void} method that merges data from a source {@link JsonObject} into a target {@link JsonObject}. *

    * If the target JSON object already contains a key present in the source JSON object, the values are merged if they are arrays. * Otherwise, the source value is added to the target JSON object. @@ -36,15 +37,15 @@ public class JSONMerger { * * @param pTarget {@link JsonObject} - The target {@link JsonObject} to merge data into. This object will be modified by adding or updating its values. * @param pSource {@link JsonObject} - The source {@link JsonObject} to merge data from. This object is not modified by the operation. - * @author MeAlam - * @Co-author Dan - * @since 1.0.0 */ public void mergeJsonObjects(JsonObject pTarget, JsonObject pSource) { + for (Map.Entry entry : pSource.entrySet()) { - if (pTarget.has(entry.getKey())) { - JsonElement targetElement = pTarget.get(entry.getKey()); - JsonElement sourceElement = entry.getValue(); + String key = entry.getKey(); + JsonElement sourceElement = entry.getValue(); + + if (pTarget.has(key)) { + JsonElement targetElement = pTarget.get(key); if (targetElement.isJsonArray() && sourceElement.isJsonArray()) { JsonArray targetArray = targetElement.getAsJsonArray(); @@ -53,11 +54,15 @@ public void mergeJsonObjects(JsonObject pTarget, JsonObject pSource) { for (JsonElement element : sourceArray) { targetArray.add(element); } + + BaseLogger.log(BaseLogLevel.ERROR, "Merged array for key: " + key, true); } else { - pTarget.add(entry.getKey(), sourceElement); + pTarget.add(key, sourceElement); + BaseLogger.log(BaseLogLevel.WARNING, "Overwriting value for key: " + key, true); } } else { - pTarget.add(entry.getKey(), entry.getValue()); + pTarget.add(key, sourceElement); + BaseLogger.log(BaseLogLevel.SUCCESS, "Added new key: " + key, true); } } } diff --git a/forge/src/main/java/software/bluelib/platform/ForgePlatformHelper.java b/forge/src/main/java/software/bluelib/platform/ForgePlatformHelper.java new file mode 100644 index 00000000..d941bb87 --- /dev/null +++ b/forge/src/main/java/software/bluelib/platform/ForgePlatformHelper.java @@ -0,0 +1,66 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.platform; + +import net.minecraftforge.fml.ModList; +import net.minecraftforge.fml.loading.FMLLoader; +import software.bluelib.interfaces.platform.IPlatformHelper; + +/** + * A {@code public class} that provides platform-specific implementation for Forge. + *

    + * This class implements {@link IPlatformHelper} to provide Forge-specific functionality such as + * retrieving the platform name, checking if a mod is loaded, and determining if the game is running + * in a development environment. + *

    + * + * Key Methods: + *
      + *
    • {@link #getPlatformName()} - Returns the platform name for Forge.
    • + *
    • {@link #isModLoaded(String)} - Checks if a mod is loaded using Forge's {@link ModList}.
    • + *
    • {@link #isDevelopmentEnvironment()} - Checks if Forge is running in a development environment.
    • + *
    + * + * @author MeAlam + * @since 1.0.0 + */ +public class ForgePlatformHelper implements IPlatformHelper { + + /** + * A {@code public} {@link String} method that returns the name of the current platform, which is "Forge" for this implementation. + * + * @return {@link String} - The platform name, "Forge". + * @author MeAlam + * @since 1.0.0 + */ + @Override + public String getPlatformName() { + return "Forge"; + } + + /** + * A {@code public} {@link Boolean} method that checks if a mod with the given ID is loaded using Forge's {@link ModList}. + * + * @param pModId {@link String} - The mod ID to check if it's loaded. + * @return {@code true} if the mod is loaded, {@code false} if it isn't. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public boolean isModLoaded(String pModId) { + return ModList.get().isLoaded(pModId); + } + + /** + * A {@code public} {@link Boolean} method that checks if the game is currently running in a development environment + * using Forge's {@link FMLLoader}. + * + * @return {@code true} if running in a development environment, {@code false} if it isn't. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public boolean isDevelopmentEnvironment() { + return !FMLLoader.isProduction(); + } +} diff --git a/forge/src/main/java/software/bluelib/utils/minecraft/ChunkUtils.java b/forge/src/main/java/software/bluelib/utils/minecraft/ChunkUtils.java new file mode 100644 index 00000000..52704d81 --- /dev/null +++ b/forge/src/main/java/software/bluelib/utils/minecraft/ChunkUtils.java @@ -0,0 +1,233 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.utils.minecraft; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.chunk.LevelChunk; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +import java.util.Arrays; +import java.util.Collection; +import java.util.stream.Collectors; + +/** + * A {@code class} providing methods to interact with Minecraft chunks, + * specifically for retrieving biome and tile entity information. + *

    + * Key Methods: + *

      + *
    • {@link #getBiomeOfChunk(Level, ChunkPos)} - Retrieves the {@link Biome} of the specified chunk.
    • + *
    • {@link #getBiomeRegistryNameOfChunk(Level, ChunkPos)} - Retrieves the biome registry name of the specified chunk.
    • + *
    • {@link #getBiomeSimpleNameOfChunk(Level, ChunkPos)} - Retrieves the simple name of the biome in the specified chunk.
    • + *
    • {@link #getChunkTileEntities(Level, ChunkPos)} - Retrieves the tile entities within the specified chunk.
    • + *
    • {@link #getChunkTileEntitiesRegistryNames(Level, ChunkPos)} - Retrieves the registry names of tile entities in the specified chunk.
    • + *
    • {@link #getChunkTileEntitiesSimpleNames(Level, ChunkPos)} - Retrieves the simple names of tile entities in the specified chunk.
    • + *
    • {@link #getChunkBlockCount(Level, ChunkPos)} - Counts the number of non-air blocks in the specified chunk.
    • + *
    + * + * @author MeAlam + * @since 1.0.0 + */ +public class ChunkUtils { + + /** + * Private constructor to prevent instantiation. + *

    + * This constructor is intentionally empty to prevent creating instances of this utility class. + *

    + * + * @author MeAlam + * @since 1.0.0 + */ + private ChunkUtils() { + } + + /** + * A {@link Biome} that retrieves the {@link Biome} of the specified chunk. + *

    + * Logs a success message if the biome is retrieved successfully, + * and an error message if an exception occurs. + *

    + * + * @param pLevel {@link Level} - The game world level. + * @param pChunkPos {@link ChunkPos} - The position of the chunk. + * @return The {@link Biome} associated with the specified chunk. + * @throws RuntimeException if there is an error retrieving the biome. + */ + public static Biome getBiomeOfChunk(Level pLevel, ChunkPos pChunkPos) { + try { + return pLevel.getBiome(pChunkPos.getWorldPosition()).value(); + } catch (Exception pException) { + BaseLogger.log(BaseLogLevel.ERROR, "Error retrieving biome for chunk at position " + pChunkPos, pException, true); + throw pException; + } + } + + + /** + * A {@link String} that retrieves the biome registry name of the specified chunk. + *

    + * Example: "minecraft:plains", "minecraft:desert" + *

    + * + * @param pLevel {@link Level} - The game world level. + * @param pChunkPos {@link ChunkPos} - The position of the chunk. + * @return The registry name of the chunk's biome as a {@link String}. + * @throws RuntimeException if there is an error retrieving the biome registry name. + */ + public static String getBiomeRegistryNameOfChunk(Level pLevel, ChunkPos pChunkPos) { + ResourceLocation biomeKey = pLevel.registryAccess() + .registryOrThrow(Registries.BIOME) + .getKey(pLevel.getBiome(pChunkPos.getWorldPosition()).value()); + + if (biomeKey == null) { + NullPointerException exception = new NullPointerException("Biome at chunk position " + pChunkPos + " is null"); + BaseLogger.log(BaseLogLevel.ERROR, "Error retrieving biome registry name of chunk at " + pChunkPos, exception, true); + return exception.getMessage(); + } + return biomeKey.toString(); + } + + + /** + * A {@link String} that retrieves the simple name of the biome in the specified chunk. + *

    + * Example: "plains", "desert" + *

    + * + * @param pLevel {@link Level} - The game world level. + * @param pChunkPos {@link ChunkPos} - The position of the chunk. + * @return The simple name of the chunk's biome. + */ + public static String getBiomeSimpleNameOfChunk(Level pLevel, ChunkPos pChunkPos) { + String registryName = getBiomeRegistryNameOfChunk(pLevel, pChunkPos); + return registryName.contains(":") ? registryName.split(":")[1] : registryName; + } + + /** + * A {@link Collection} that retrieves the tile entities within the specified chunk. + *

    + * Logs a success message with the number of tile entities retrieved, + * and an error message if an exception occurs. + *

    + * + * @param pLevel {@link Level} - The game world level. + * @param pChunkPos {@link ChunkPos} - The position of the chunk. + * @return A collection of tile entities present in the specified chunk. + * @throws RuntimeException if there is an error retrieving tile entities. + */ + public static Collection getChunkTileEntities(Level pLevel, ChunkPos pChunkPos) { + try { + LevelChunk chunk = pLevel.getChunk(pChunkPos.x, pChunkPos.z); + return chunk.getBlockEntities().values(); + } catch (Exception pException) { + BaseLogger.log(BaseLogLevel.ERROR, "Error retrieving tile entities for chunk at position " + pChunkPos, pException, true); + throw pException; + } + } + + + /** + * A {@link String} that retrieves the registry names of tile entities in the specified chunk. + *

    + * Example: "minecraft:chest, minecraft:furnace" + *

    + * + * @param pLevel {@link Level} - The game world level. + * @param pChunkPos {@link ChunkPos} - The position of the chunk. + * @return A comma-separated string of tile entity registry names in the chunk. + * @throws RuntimeException if there is an error retrieving tile entity registry names. + */ + public static String getChunkTileEntitiesRegistryNames(Level pLevel, ChunkPos pChunkPos) { + try { + Collection blockEntities = getChunkTileEntities(pLevel, pChunkPos); + + return blockEntities.stream() + .map(blockEntity -> { + ResourceLocation key = pLevel.registryAccess() + .registryOrThrow(Registries.BLOCK_ENTITY_TYPE) + .getKey(blockEntity.getType()); + + return key != null ? key.toString() : "unknown"; + }) + .collect(Collectors.joining(", ")); + } catch (Exception pException) { + BaseLogger.log(BaseLogLevel.ERROR, "Error retrieving tile entity registry names for chunk at position " + pChunkPos, pException, true); + throw pException; + } + } + + /** + * A {@link String} that retrieves the simple names of tile entities in the specified chunk. + *

    + * Example: "chest, furnace" + *

    + * + * @param pLevel {@link Level} - The game world level. + * @param pChunkPos {@link ChunkPos} - The position of the chunk. + * @return A comma-separated string of tile entity simple names in the chunk. + */ + public static String getChunkTileEntitiesSimpleNames(Level pLevel, ChunkPos pChunkPos) { + String registryNames = getChunkTileEntitiesRegistryNames(pLevel, pChunkPos); + + return Arrays.stream(registryNames.split(", ")) + .map(fullName -> fullName.contains(":") ? fullName.split(":")[1] : fullName) + .collect(Collectors.joining(", ")); + } + + /** + * A {@link Integer} that counts the number of non-air blocks in the specified chunk. + *

    + * Logs a success message with the block count, + * and an error message if an exception occurs. + *

    + * + * @param pLevel {@link Level} - The game world level. + * @param pChunkPos {@link ChunkPos} - The position of the chunk. + * @return The number of non-air blocks in the specified chunk. + * @throws RuntimeException if there is an error counting blocks. + */ + public static int getChunkBlockCount(Level pLevel, ChunkPos pChunkPos) { + try { + LevelChunk chunk = pLevel.getChunk(pChunkPos.x, pChunkPos.z); + int blockCount = 0; + + for (int x = 0; x < 16; x++) { + for (int y = pLevel.getMinBuildHeight(); y < pLevel.getHeight(); y++) { + for (int z = 0; z < 16; z++) { + BlockPos worldPos = new BlockPos(pChunkPos.getMinBlockX() + x, y, pChunkPos.getMinBlockZ() + z); + if (!chunk.getBlockState(worldPos).isAir()) { + blockCount++; + } + } + } + } + return blockCount; + } catch (Exception pException) { + BaseLogger.log(BaseLogLevel.ERROR, "Error counting blocks for chunk at position " + pChunkPos, pException, true); + throw pException; + } + } + + /** FIXME: This method is not working as expected. It is not returning correctly. + public static boolean isChunkLoaded(final LevelAccessor pWorld, final int pX, final int pZ) { + try { + boolean isLoaded = pWorld.getChunk(pX, pZ, ChunkStatus.FULL, false) != null; + BaseLogger.bluelibLogSuccess("Chunk at (" + pX + ", " + pZ + ") is loaded: " + isLoaded); + return isLoaded; + } catch (Exception e) { + BaseLogger.logError("Error checking if chunk at (" + pX + ", " + pZ + ") is loaded", e); + return false; + } + } + */ + + +} diff --git a/forge/src/main/java/software/bluelib/utils/variant/ParameterUtils.java b/forge/src/main/java/software/bluelib/utils/variant/ParameterUtils.java new file mode 100644 index 00000000..da316636 --- /dev/null +++ b/forge/src/main/java/software/bluelib/utils/variant/ParameterUtils.java @@ -0,0 +1,184 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.utils.variant; + +import software.bluelib.entity.variant.VariantLoader; +import software.bluelib.entity.variant.VariantParameter; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; + +/** + * A utility class for managing custom parameters associated with entity variants. + *

    + * Provides methods to retrieve custom parameters for variants and allows for + * building and connecting parameters to specific variants via the {@link ParameterBuilder} class. + *

    + *

    + * Key Methods: + *

      + *
    • {@link #getParameter(String, String)} - Retrieves the value of a custom parameter for a specific variant.
    • + *
    + *

    + * Nested Classes: + *

      + *
    • {@link ParameterBuilder} - Builder class for creating and associating custom parameters with variants.
    • + *
    + * + * @author MeAlam + * @version 1.0.0 + * @see VariantParameter + * @since 1.0.0 + */ +public class ParameterUtils { + + /** + * Private constructor to prevent instantiation. + *

    + * This constructor is intentionally empty to prevent creating instances of this utility class. + *

    + * + * @author MeAlam + * @since 1.0.0 + */ + private ParameterUtils() { + } + + /** + * Holds custom parameters for each variant. + *

    + * The outer map's key is the variant name, and the inner map contains key-value pairs + * representing custom parameters for that variant. + *

    + * + * @since 1.0.0 + */ + private static final Map> variantParametersMap = new HashMap<>(); + + /** + * A {@link String} that retrieves the value of a custom parameter for a specific variant. + *

    + * If the parameter is not found, {@code null} is returned. + *

    + * + * @param pVariantName {@link String} The name of the variant. + * @param pParameterKey {@link String} The key of the parameter to retrieve. + * @return {@link String} The value of the custom parameter for the specified variant or {@code null} if not found. + * @author MeAlam + * @since 1.0.0 + */ + public static String getParameter(String pVariantName, String pParameterKey) { + return variantParametersMap.getOrDefault(pVariantName, new HashMap<>()).getOrDefault(pParameterKey, "null"); + } + + /** + * A {@code class} for creating and associating custom parameters with a specific variant. + *

    + * Allows chaining methods to build and connect parameters to a variant. + *

    + *

    + * Key Methods: + *

      + *
    • {@link #forVariant(String, String)} - Creates a new instance of {@link ParameterBuilder} for a specific entity and variant.
    • + *
    • {@link #withParameter(String)} - Adds a parameter with a default value of {@code null} .
    • + *
    • {@link #connect()} - Connects the parameters to the variant and updates {@link VariantParameter} with the parameters.
    • + *
    + * + * @author MeAlam + * @since 1.0.0 + */ + public static class ParameterBuilder { + + /** + * The name of the variant being associated with custom parameters. + * + * @since 1.0.0 + */ + private final String variantName; + + /** + * The name of the entity being associated with custom parameters. + * + * @since 1.0.0 + */ + private final String entityName; + + /** + * Stores custom parameters being built for the variant. + * + * @since 1.0.0 + */ + private final Map parameters = new HashMap<>(); + + /** + * Constructor to initialize the builder for a specific entity and variant. + * + * @param pEntityName {@link String} The name of the entity. + * @param pVariantName {@link String} The name of the variant. + * @author MeAlam + * @since 1.0.0 + */ + private ParameterBuilder(String pEntityName, String pVariantName) { + this.variantName = pVariantName; + this.entityName = pEntityName; + } + + /** + * A {@link ParameterBuilder} that creates a new instance of {@link ParameterBuilder} for the specified entity and variant. + * + * @param pEntityName {@link String} The name of the entity. + * @param pVariantName {@link String} The name of the variant. + * @return {@link ParameterBuilder} A new instance for chaining. + * @author MeAlam + * @since 1.0.0 + */ + public static ParameterBuilder forVariant(String pEntityName, String pVariantName) { + return new ParameterBuilder(pEntityName, pVariantName); + } + + /** + * A {@link ParameterBuilder} that adds a custom parameter to the builder with a default value of "null". + *

    + * The {@code null} value is used if the parameter is not specified in the data source. + *

    + * + * @param pParameter {@link String} The parameter key. + * @return {@link ParameterBuilder} The builder instance for chaining. + * @author MeAlam + * @since 1.0.0 + */ + public ParameterBuilder withParameter(String pParameter) { + parameters.put(pParameter, "null"); + return this; + } + + /** + * A {@link ParameterBuilder} that connects the custom parameters to the specified variant and updates the {@link VariantParameter}. + *

    + * Throws a {@link NoSuchElementException} if the variant or entity is not found. + *

    + * + * @return {@link ParameterBuilder} The builder instance for chaining. + * @throws NoSuchElementException if the variant or entity is not found in the database. + * @author MeAlam + * @since 1.0.0 + */ + public ParameterBuilder connect() { + VariantParameter variant = VariantLoader.getVariantByName(entityName, variantName); + if (variant != null) { + Map updatedParameters = new HashMap<>(); + for (String key : parameters.keySet()) { + updatedParameters.put(key, variant.getParameter(key)); + } + variantParametersMap.put(variantName, updatedParameters); + } else { + Throwable throwable = new Throwable("Variant or entity not found in the database"); + BaseLogger.log(BaseLogLevel.ERROR, "Variant '" + variantName + "' not found for entity '" + entityName + "'", throwable, true); + } + return this; + } + } +} diff --git a/Forge/src/main/resources/META-INF/mods.toml b/forge/src/main/resources/META-INF/mods.toml similarity index 54% rename from Forge/src/main/resources/META-INF/mods.toml rename to forge/src/main/resources/META-INF/mods.toml index d79a67b3..4ce9fb30 100644 --- a/Forge/src/main/resources/META-INF/mods.toml +++ b/forge/src/main/resources/META-INF/mods.toml @@ -1,25 +1,25 @@ modLoader="javafml" -loaderVersion="${loader_version_range}" -license="${mod_license}" +loaderVersion="${forge_loader_version_range}" +license="${license}" [[mods]] modId="${mod_id}" -version="${mod_version}" +version="${version}" displayName="${mod_name}" -displayURL="https://github.com/MeAlam1/BlueLib/wiki" -#logoFile="examplemod.png" +displayURL="https://mealam1.github.io/BlueLib/" +logoFile="bluelib.png" credits="Anyone who contributed to the Source Code of BlueLib!" -authors="${mod_authors}" -description='''${mod_description}''' +authors="${mod_author}" +description='''${description}''' -[[dependencies.${mod_id}]] +[[dependencies.bluelib]] modId="forge" mandatory=true versionRange="${forge_version_range}" ordering="NONE" side="BOTH" -[[dependencies.${mod_id}]] +[[dependencies.bluelib]] modId="minecraft" mandatory=true versionRange="${minecraft_version_range}" diff --git a/forge/src/main/resources/META-INF/services/software.bluelib.interfaces.platform.IPlatformHelper b/forge/src/main/resources/META-INF/services/software.bluelib.interfaces.platform.IPlatformHelper new file mode 100644 index 00000000..0031c488 --- /dev/null +++ b/forge/src/main/resources/META-INF/services/software.bluelib.interfaces.platform.IPlatformHelper @@ -0,0 +1 @@ +software.bluelib.platform.ForgePlatformHelper \ No newline at end of file diff --git a/NeoForge/src/main/resources/assets/bluelib/animations/dragon.animation.json b/forge/src/main/resources/assets/bluelib/animations/dragon.animation.json similarity index 100% rename from NeoForge/src/main/resources/assets/bluelib/animations/dragon.animation.json rename to forge/src/main/resources/assets/bluelib/animations/dragon.animation.json diff --git a/NeoForge/src/main/resources/assets/bluelib/animations/rex.animation.json b/forge/src/main/resources/assets/bluelib/animations/rex.animation.json similarity index 100% rename from NeoForge/src/main/resources/assets/bluelib/animations/rex.animation.json rename to forge/src/main/resources/assets/bluelib/animations/rex.animation.json diff --git a/NeoForge/src/main/resources/assets/bluelib/geo/dragon.geo.json b/forge/src/main/resources/assets/bluelib/geo/dragon.geo.json similarity index 100% rename from NeoForge/src/main/resources/assets/bluelib/geo/dragon.geo.json rename to forge/src/main/resources/assets/bluelib/geo/dragon.geo.json diff --git a/NeoForge/src/main/resources/assets/bluelib/geo/rex.geo.json b/forge/src/main/resources/assets/bluelib/geo/rex.geo.json similarity index 100% rename from NeoForge/src/main/resources/assets/bluelib/geo/rex.geo.json rename to forge/src/main/resources/assets/bluelib/geo/rex.geo.json diff --git a/NeoForge/src/main/resources/assets/bluelib/lang/en_us.json b/forge/src/main/resources/assets/bluelib/lang/en_us.json similarity index 100% rename from NeoForge/src/main/resources/assets/bluelib/lang/en_us.json rename to forge/src/main/resources/assets/bluelib/lang/en_us.json diff --git a/forge/src/main/resources/assets/bluelib/textures/entity/dragon/blue.png b/forge/src/main/resources/assets/bluelib/textures/entity/dragon/blue.png new file mode 100644 index 0000000000000000000000000000000000000000..83be2147b8db3aaab415e12dc3b6e9a11a594ea4 GIT binary patch literal 676 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!7>k44ofvPP)Tsw@I14-?iy0WW zg+Z8+Vb&Z8pkQEtPlzj!7Pn|)P)=iz4q{MmaJHmZ`5KTT zT@vIM%(v z(aRpoCoWyiTgojaD&F|F!PdZVM@UbNjptVBj4jLF?f%O$LFQ-3hJ{yuCnpH{RqTF0 zeUaRrN`{moTZcM{69EVLnQmQO%s%OtOoW(UeeqPr*})2&$=17>Km8Ke@%eU3;;UAl z_dE-lA2QvTo9bV8@y{Lo#EEr$XX6_ew|<>1!7nqtJ8%c@3EiH9*VZm)@~qx!CojNR zaWk9uaEODlE8kU9hhhtcCz6N4bsR43<#29rQagW3>HO}AsXY7mmmhHEtgR04`g-ue zwv#SzoSD}|v`Lv6asM;6d&}sM&)V{9@7_H|f67<|ydtWYcW8+%(7BtgsyBPB&W+HL z{c<<=%Uw8|weJPP@nd2)_peY@i7iud+B+%Zp5z_{-H+UVy3Ca989GZ6MLiBbQ|UWb z(Y^fs4MF?eSt1usd41cW*!(Qw)s%f3B9+u@XC5>Aq_Xz;j^mF{Oz1waD#L%1(lgZ; z3|#E;@8(@%^IqToRn(#6{C~ELFSiAJm_Id{T)EuXZp3k!(UWyjbJC~xst$5b_e=bf q?_)BNT$9h7r)_M&3`GSo`xu+NS2^bO6ubn+JAk44ofvPP)Tsw@I14-?iy0WW zg+Z8+Vb&Z8pkQEtPlzj!Zjwt65OA~RHu2!IKd|xH`;UJotCY^rs5}_f`{vw>EiO$N z3LOzw<`r~)`(`)e=Yh7PEk`d-JKbu&a%s|urSjIR6`Z;x41ng`DxW(eV9(;TfP5fF zx+KUinBhN8a3r(H6zIsKo-U3d6?5KRy`8t%K!EMR{p2K$y_w2uzx|D07t}vHbT_ZS zqnACFPh7g3x0G8Pb3e8>o{E6%i-MMq;~$6()ryJQ+f9BFF)YSSz8_8_4VL` zZ6{scI5V$_Xp=HC;{IoB_m!M8-n(`vqUbO^7^(#vH4lVt10_7L@KG*&OB!JNoDQx9mgM^n9zM-Rfhj2rDv)y z7`WKw-_5(k=DoiEtEfZC`TuMgUv3NdFn?+?xpKL&-H78dqbKX6=A=*WRUPD>?w9x{ q-^XMkxh9`EPutjl8Hx&G_AxejuX4=mDR>EtcLq;aKbLh*2~7ZCIW7SJ literal 0 HcmV?d00001 diff --git a/NeoForge/src/main/resources/assets/bluelib/textures/entity/rex/brown.png b/forge/src/main/resources/assets/bluelib/textures/entity/rex/brown.png similarity index 100% rename from NeoForge/src/main/resources/assets/bluelib/textures/entity/rex/brown.png rename to forge/src/main/resources/assets/bluelib/textures/entity/rex/brown.png diff --git a/NeoForge/src/main/resources/assets/bluelib/textures/entity/rex/green.png b/forge/src/main/resources/assets/bluelib/textures/entity/rex/green.png similarity index 100% rename from NeoForge/src/main/resources/assets/bluelib/textures/entity/rex/green.png rename to forge/src/main/resources/assets/bluelib/textures/entity/rex/green.png diff --git a/forge/src/main/resources/data/bluelib.variant/entity/dragon/blue.json b/forge/src/main/resources/data/bluelib.variant/entity/dragon/blue.json new file mode 100644 index 00000000..1125138b --- /dev/null +++ b/forge/src/main/resources/data/bluelib.variant/entity/dragon/blue.json @@ -0,0 +1,14 @@ +{ + "dragon": [ + { + "variantName": "blue", + "customParameter": "customValue3", + "int": 1, + "bool": true, + "array": [ + "boop", + "blep" + ] + } + ] +} \ No newline at end of file diff --git a/NeoForge/src/main/resources/data/bluelib/variant/entity/dragon.json b/forge/src/main/resources/data/bluelib.variant/entity/dragon/dragon.json similarity index 100% rename from NeoForge/src/main/resources/data/bluelib/variant/entity/dragon.json rename to forge/src/main/resources/data/bluelib.variant/entity/dragon/dragon.json diff --git a/forge/src/main/resources/data/bluelib.variant/entity/dragon/pink.json b/forge/src/main/resources/data/bluelib.variant/entity/dragon/pink.json new file mode 100644 index 00000000..4133639c --- /dev/null +++ b/forge/src/main/resources/data/bluelib.variant/entity/dragon/pink.json @@ -0,0 +1,14 @@ +{ + "dragon": [ + { + "variantName": "pink", + "customParameter": "customValue3", + "int": 1, + "bool": true, + "array": [ + "boop", + "blep" + ] + } + ] +} \ No newline at end of file diff --git a/NeoForge/src/main/resources/data/bluelib/variant/entity/rex.json b/forge/src/main/resources/data/bluelib.variant/entity/rex/rex.json similarity index 100% rename from NeoForge/src/main/resources/data/bluelib/variant/entity/rex.json rename to forge/src/main/resources/data/bluelib.variant/entity/rex/rex.json diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000..8af79e46 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,32 @@ +# Important Notes: +# Every field you add must be added to the root build.gradle expandProps map. + +# Common +minecraft_version=1.20.2 +mod_id=bluelib +mod_name=BlueLib +license=MIT License +version=1.0.0 +group=software.bluelib +mod_author=Dan, Aram +description=BlueLib is an All round Minecraft mod library that offers data-driven features, allowing users to implement and customize its features with full freedom. \nIt supports both Resource and Datapacks, ensuring seamless integration and flexibility. +credits= + +minecraft_version_range=[1.20.2, 1.21) + +# Fabric +fabric_version=0.91.1+1.20.2 +fabric_loader_version=0.15.0 + +# Forge +forge_version=48.0.49 +forge_loader_version_range=[48,) +forge_version_range=[48,) + +# NeoForge +neoforge_version=20.2.86 +neoforge_loader_version_range=[1,) + +# Gradle +org.gradle.jvmargs=-Xmx3G +org.gradle.daemon=false diff --git a/NeoForge/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar similarity index 89% rename from NeoForge/gradle/wrapper/gradle-wrapper.jar rename to gradle/wrapper/gradle-wrapper.jar index 2c3521197d7c4586c843d1d3e9090525f1898cde..a4b76b9530d66f5e68d973ea569d8e19de379189 100644 GIT binary patch delta 3990 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 diff --git a/NeoForge/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties similarity index 93% rename from NeoForge/gradle/wrapper/gradle-wrapper.properties rename to gradle/wrapper/gradle-wrapper.properties index 09523c0e..df97d72b 100644 --- a/NeoForge/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.9-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/NeoForge/gradlew b/gradlew similarity index 100% rename from NeoForge/gradlew rename to gradlew diff --git a/NeoForge/gradlew.bat b/gradlew.bat similarity index 100% rename from NeoForge/gradlew.bat rename to gradlew.bat diff --git a/neoforge/build.gradle b/neoforge/build.gradle new file mode 100644 index 00000000..9f16e30a --- /dev/null +++ b/neoforge/build.gradle @@ -0,0 +1,91 @@ +plugins { + id 'idea' + id 'maven-publish' + id 'net.neoforged.gradle.userdev' version '7.0.41' + id 'java-library' +} +base { + archivesName = "${mod_name}-neoforge-${minecraft_version}" +} + +repositories { + exclusiveContent { + forRepository { + maven { + url "https://cursemaven.com" + } + } + filter { + includeGroup "curse.maven" + } + } +} + +// Automatically enable neoforge AccessTransformers if the file exists +// This location is hardcoded in FML and can not be changed. +// https://github.com/neoforged/FancyModLoader/blob/a952595eaaddd571fbc53f43847680b00894e0c1/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModFile.java#L118 +if (file('src/main/resources/META-INF/accesstransformer.cfg').exists()) { + minecraft.accessTransformers.file file('src/main/resources/META-INF/accesstransformer.cfg') +} +runs { + configureEach { + modSource project.sourceSets.main + } + client { + systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id + } + server { + systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id + programArgument '--nogui' + } + + gameTestServer { + systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id + } + + data { + programArguments.addAll '--mod', project.mod_id, '--all', '--output', file('src/generated/resources/').getAbsolutePath(), '--existing', file('src/main/resources/').getAbsolutePath() + } +} + +sourceSets.main.resources { srcDir 'src/generated/resources' } + +dependencies { + implementation "net.neoforged:neoforge:${neoforge_version}" + compileOnly project(":common") + compileOnly("curse.maven:geckolib-388172:4936009") + runtimeOnly("curse.maven:geckolib-388172:4936009") +} + +// NeoGradle compiles the game, but we don't want to add our common code to the game's code +Spec notNeoTask = { Task it -> !it.name.startsWith("neo") } as Spec + +tasks.withType(JavaCompile).matching(notNeoTask).configureEach { + source(project(":common").sourceSets.main.allSource) +} + +tasks.withType(Javadoc).matching(notNeoTask).configureEach { + source(project(":common").sourceSets.main.allJava) +} + +tasks.named("sourcesJar", Jar) { + from(project(":common").sourceSets.main.allSource) +} + +tasks.withType(ProcessResources).matching(notNeoTask).configureEach { + from project(":common").sourceSets.main.resources +} + +publishing { + publications { + mavenJava(MavenPublication) { + artifactId base.archivesName.get() + from components.java + } + } + repositories { + maven { + url "file://" + System.getenv("local_maven") + } + } +} diff --git a/neoforge/src/main/java/software/bluelib/BlueLib.java b/neoforge/src/main/java/software/bluelib/BlueLib.java new file mode 100644 index 00000000..a294bf24 --- /dev/null +++ b/neoforge/src/main/java/software/bluelib/BlueLib.java @@ -0,0 +1,70 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib; + +import net.neoforged.bus.api.IEventBus; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.ModContainer; +import net.neoforged.fml.common.Mod; +import net.neoforged.fml.event.lifecycle.FMLLoadCompleteEvent; +import net.neoforged.fml.loading.FMLEnvironment; +import org.spongepowered.asm.launch.MixinBootstrap; +import software.bluelib.example.event.ClientEvents; +import software.bluelib.example.init.ModEntities; + +/** + * The main class of the {@code BlueLib} mod. + *

    + * This class serves as the entry point for the {@code BlueLib} mod, handling initialization by registering event handlers + * and setting up necessary configurations. For more details, refer to the BlueLib Wiki. + *

    + * + *

    + * Key Methods: + *

      + *
    • {@link #BlueLib(IEventBus, ModContainer)} - Constructs the {@code BlueLib} instance and registers the mod event bus.
    • + *
    • {@link #onLoadComplete(FMLLoadCompleteEvent)} - Handles the event when the mod loading is complete.
    • + *
    + * + * @author MeAlam, Dan and All Contributors of BlueLib! + * @see BlueLib Wiki + * @since 1.0.0 + */ +@Mod(BlueLibConstants.MOD_ID) +public class BlueLib { + + /** + * Constructs a new {@code BlueLib} instance and registers the mod event bus. + *

    + * Registers necessary mod event listeners, and if in developer mode, additional client-side listeners for rendering and attributes. + *

    + * + * @param pModEventBus {@link IEventBus} - The event bus where the mod registers its handlers. + * @param pModContainer {@link ModContainer} - The mod container that holds the instance of the mod. + * @author MeAlam + * @since 1.0.0 + */ + public BlueLib(IEventBus pModEventBus, ModContainer pModContainer) { + pModEventBus.register(this); + MixinBootstrap.init(); + if (BlueLibCommon.isDeveloperMode() && BlueLibCommon.PLATFORM.isModLoaded("geckolib") && BlueLibConstants.isExampleEnabled) { + ModEntities.REGISTRY.register(pModEventBus); + if (FMLEnvironment.dist.isClient()) { + pModEventBus.addListener(ClientEvents::registerAttributes); + pModEventBus.addListener(ClientEvents::registerRenderers); + } + } + } + + /** + * A {@code public void} that handles the {@link FMLLoadCompleteEvent}, which occurs when the mod finishes loading. + * + * @param pEvent {@link FMLLoadCompleteEvent} - The event fired after the mod loading process completes. + * @author MeAlam + * @since 1.0.0 + */ + @SubscribeEvent + public void onLoadComplete(FMLLoadCompleteEvent pEvent) { + BlueLibCommon.init(); + } +} diff --git a/neoforge/src/main/java/software/bluelib/entity/variant/VariantLoader.java b/neoforge/src/main/java/software/bluelib/entity/variant/VariantLoader.java new file mode 100644 index 00000000..f8257571 --- /dev/null +++ b/neoforge/src/main/java/software/bluelib/entity/variant/VariantLoader.java @@ -0,0 +1,189 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.entity.variant; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.packs.resources.ResourceManager; +import software.bluelib.interfaces.variant.base.IVariantEntityBase; +import software.bluelib.json.JSONLoader; +import software.bluelib.json.JSONMerger; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +import java.util.*; + +/** + * A {@code public class} that implements the {@link IVariantEntityBase} {@code interface} that manages the loading and storage of entity variants. + *

    + * The class handles loading and merging of JSON Data by utilizing the {@link JSONLoader} and {@link JSONMerger} classes.
    + * To load the Variants it loops through all resources in a folder and merges them into a single {@link JsonObject}.
    + * The merged JSON data is then parsed into {@link VariantParameter} instances and stored in {@link #entityVariantsMap}.
    + *

    + * Key Methods: + *
      + *
    • {@link #loadVariants(String, MinecraftServer, String)} - Loads and merges variant data by looping thru all resources in a folder.
    • + *
    • {@link #getVariantsFromEntity(String)} - Retrieves the list of loaded {@link VariantParameter} for a specific entity.
    • + *
    • {@link #getVariantByName(String, String)} - Retrieves a specific {@link VariantParameter} by its name for a given entity.
    • + *
    + * + * @author MeAlam + * @since 1.0.0 + */ +public class VariantLoader implements IVariantEntityBase { + + /** + * A {@code private static final} {@link Map} to store entity variants as key-value pairs. + *

    + * This {@link Map} holds entity names and their corresponding list of {@link VariantParameter} instances. + *

    + * + * @since 1.0.0 + */ + private static final Map> entityVariantsMap = new HashMap<>(); + + /** + * A {@code private static final} {@link JSONLoader} to load JSON data from resources. + * + * @since 1.0.0 + */ + private static final JSONLoader jsonLoader = new JSONLoader(); + + /** + * A {@code private static final} {@link JSONMerger} to merge JSON data. + *

    + * This {@link JSONMerger} instance is used to merge JSON data into a single {@link JsonObject}. + *

    + * + * @since 1.0.0 + */ + private static final JSONMerger jsonMerger = new JSONMerger(); + + /** + * A {@code public static void} that loads and merges variant data from JSON resources in the specified folder path. + *

    + * The method loops through all resources in the folder and merges them into a single {@link JsonObject}.
    + * The merged JSON data is then parsed into {@link VariantParameter} instances and stored in {@link #entityVariantsMap}. + *

    + * + * @param pFolderPath {@link String} - The path to the folder containing JSON resources. + * @param pServer {@link MinecraftServer} - The {@link MinecraftServer} instance used to access resources. + * @param pEntityName {@link String} - The name of the entity whose variants should be cleared before loading new ones. + */ + public static void loadVariants(String pFolderPath, MinecraftServer pServer, String pEntityName) { + + clearVariantsForEntity(pEntityName); + + ResourceManager resourceManager = pServer.getResourceManager(); + JsonObject mergedJsonObject = new JsonObject(); + + Collection collection = resourceManager.listResources(pFolderPath, pFiles -> pFiles.getPath().endsWith(".json")).keySet(); + + BaseLogger.log(BaseLogLevel.INFO, "Found resources: " + collection + " at: " + pFolderPath + " for: " + pEntityName, true); + + for (ResourceLocation resourceLocation : collection) { + try { + BaseLogger.log(BaseLogLevel.INFO, "Loading JSON data from resource: " + resourceLocation.toString(), true); + JsonObject jsonObject = jsonLoader.loadJson(resourceLocation, resourceManager); + jsonMerger.mergeJsonObjects(mergedJsonObject, jsonObject); + } catch (Exception pException) { + BaseLogger.log(BaseLogLevel.ERROR, "Failed to load JSON data from resource: " + resourceLocation.toString(), pException, true); + } + } + parseVariants(mergedJsonObject); + } + + /** + * A {@code private static void} that clears variants for a specific entity type from {@link #entityVariantsMap}. + *

    + * This method removes all variants associated with the given entity name. + *

    + * + * @param pEntityName {@link String} - The name of the entity whose variants should be cleared. + */ + private static void clearVariantsForEntity(String pEntityName) { + entityVariantsMap.remove(pEntityName); + } + + /** + * A {@code private static void} that parses the merged JSON data and converts it into {@link VariantParameter} instances. + *

    + * This method processes each entry in the JSON object and stores the created {@link VariantParameter} instances in {@link #entityVariantsMap}. + *

    + * + * @param pJsonObject {@link JsonObject} - The merged {@link JsonObject} containing variant data. + */ + private static void parseVariants(JsonObject pJsonObject) { + for (Map.Entry entry : pJsonObject.entrySet()) { + String entityName = entry.getKey(); + JsonArray textureArray = entry.getValue().getAsJsonArray(); + + BaseLogger.log(BaseLogLevel.INFO, "Parsing variants for entity: " + entityName, true); + List variantList = entityVariantsMap.computeIfAbsent(entityName, k -> new ArrayList<>()); + + for (JsonElement variant : textureArray) { + VariantParameter newVariant = getEntityVariant(entityName, variant.getAsJsonObject()); + + boolean variantExists = variantList.stream() + .anyMatch(v -> v.equals(newVariant)); + + if (!variantExists) { + variantList.add(newVariant); + } + } + } + } + + /** + * A {@code private static} {@link VariantParameter} that creates a new {@link VariantParameter} instance from a JSON object. + *

    + * This method wraps the creation of {@link VariantParameter} instances for easier management and potential modification. + *

    + * + * @param pJsonKey {@link String} - The key associated with this variant. + * @param pJsonObject {@link JsonObject} - The {@link JsonObject} containing the variant data. + * @return {@link VariantParameter} - A {@link VariantParameter} instance. + */ + private static VariantParameter getEntityVariant(String pJsonKey, JsonObject pJsonObject) { + return new VariantParameter(pJsonKey, pJsonObject); + } + + /** + * A {@code public static} {@link List} that retrieves the {@link List} of loaded {@link VariantParameter} instances for a specific entity. + *

    + * This method returns a list of variants for the given entity name. If no variants are found, an empty list is returned. + *

    + * + * @param pEntityName {@link String} - The name of the entity to retrieve variants for. + * @return {@link List} - A {@link List} of {@link VariantParameter} instances for the specified entity. + */ + public static List getVariantsFromEntity(String pEntityName) { + BaseLogger.log(BaseLogLevel.INFO, "Retrieving variants for entity: " + pEntityName, true); + return entityVariantsMap.getOrDefault(pEntityName, new ArrayList<>()); + } + + /** + * A {@code public static} {@link VariantParameter} that retrieves a {@link VariantParameter} for a specific entity, by the variant's name. + *

    + * This method searches for a variant with the specified name within the list of variants for the given entity. + *

    + * + * @param pEntityName {@link String} - The name of the entity to retrieve variants for. + * @param pVariantName {@link String} - The name of the variant to retrieve. + * @return {@link VariantParameter} - The {@link VariantParameter} with the specified name, or {@code null} if not found. + */ + public static VariantParameter getVariantByName(String pEntityName, String pVariantName) { + BaseLogger.log(BaseLogLevel.INFO, "Retrieving variant by name: " + pVariantName + " for entity: " + pEntityName, true); + List variants = getVariantsFromEntity(pEntityName); + for (VariantParameter variant : variants) { + if (variant.getVariantParameter().equals(pVariantName)) { + return variant; + } + } + BaseLogger.log(BaseLogLevel.INFO, "Variant with name: " + pVariantName + " not found for entity: " + pEntityName, true); + return null; + } +} diff --git a/neoforge/src/main/java/software/bluelib/entity/variant/VariantParameter.java b/neoforge/src/main/java/software/bluelib/entity/variant/VariantParameter.java new file mode 100644 index 00000000..a3805dba --- /dev/null +++ b/neoforge/src/main/java/software/bluelib/entity/variant/VariantParameter.java @@ -0,0 +1,174 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.entity.variant; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import software.bluelib.entity.variant.base.ParameterBase; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +import java.util.Map; +import java.util.Set; + +/** + * A {@code class} that represents the parameters associated with a specific variant of an entity. + *

    + * This class extends {@link ParameterBase} to store and manage variant-specific parameters parsed from a {@link JsonObject}. + *

    + * The class handles various JSON element types, including {@code JsonPrimitive}, {@code JsonArray}, and {@code JsonObject}. + *

    + * Key Methods: + *
      + *
    • {@link #getJsonKey()} - Retrieves the key of the JSON object that identifies this entity.
    • + *
    • {@link #getVariantParameter()} - Retrieves the name of the variant.
    • + *
    • {@link #getParameter(String)} - Retrieves the value of a specific parameter by its key.
    • + *
    + * + * @author MeAlam + * @since 1.0.0 + */ +public class VariantParameter extends ParameterBase { + + /** + * A {@code private final} {@link String} that represents the key of the JSON object that identifies this entity. + *

    + * This key is used to map the entity to its corresponding parameters within a {@link JsonObject}. + *

    + * + * @since 1.0.0 + */ + private final String jsonKey; + + /** + * A {@code private static} {@link String} that represents the name of the Variant parameter. + *

    + * This key is used to locate the variant name within the parameters/JSON files. + *

    + * + * @since 1.0.0 + */ + private static String variantParameterName = "variantName"; + + /** + * Constructs a new {@code VariantParameter} instance by extracting parameters from a given {@link JsonObject}. + *

    + * This constructor processes different types of {@link JsonElement} values: + *

      + *
    • {@link com.google.gson.JsonPrimitive}: Stored directly as a string.
    • + *
    • {@link com.google.gson.JsonArray}: Converts array elements into a single comma-separated string.
    • + *
    • {@link JsonObject}: Converts the nested JSON object to a string representation.
    • + *
    • {@code Other Types}: Stores "null" for unhandled JSON types.
    • + *
    + * + * @param pJsonKey {@link String} - The key that identifies this entity within the {@link JsonObject}. + * @param pJsonObject {@link JsonObject} - The {@link JsonObject} containing the variant parameters. + * @throws IllegalArgumentException if {@code pJsonKey} or {@code pJsonObject} is {@code null}. + * @author MeAlam + * @see ParameterBase + * @since 1.0.0 + */ + public VariantParameter(String pJsonKey, JsonObject pJsonObject) { + if (pJsonKey == null || pJsonObject == null) { + Throwable throwable = new Throwable("JSON key or JSON object is null"); + IllegalArgumentException exception = new IllegalArgumentException("JSON key and object must not be null"); + BaseLogger.log(BaseLogLevel.ERROR, exception.toString(), throwable, true); + throw exception; + } + this.jsonKey = pJsonKey; + BaseLogger.log(BaseLogLevel.INFO, "Creating VariantParameter with JSON key: " + pJsonKey, true); + Set> entryMap = pJsonObject.entrySet(); + for (Map.Entry entry : entryMap) { + JsonElement element = entry.getValue(); + if (element.isJsonPrimitive()) { + addParameter(entry.getKey(), element.getAsString()); + BaseLogger.log(BaseLogLevel.SUCCESS, "Added primitive parameter: " + entry.getKey() + " = " + element.getAsString(), true); + } else if (element.isJsonArray()) { + StringBuilder arrayValues = new StringBuilder(); + element.getAsJsonArray().forEach(e -> arrayValues.append(e.getAsString()).append(",")); + if (!arrayValues.isEmpty()) { + arrayValues.setLength(arrayValues.length() - 1); + } + addParameter(entry.getKey(), arrayValues.toString()); + BaseLogger.log(BaseLogLevel.SUCCESS, "Added array parameter: " + entry.getKey() + " = " + arrayValues, true); + } else if (element.isJsonObject()) { + addParameter(entry.getKey(), element.toString()); + BaseLogger.log(BaseLogLevel.SUCCESS, "Added object parameter: " + entry.getKey() + " = " + element, true); + } else { + addParameter(entry.getKey(), "null"); + BaseLogger.log(BaseLogLevel.SUCCESS, "Added null parameter for key: " + entry.getKey(), true); + } + } + } + + /** + * A {@link String} method that retrieves the key of the {@link JsonObject} that identifies this entity. + *

    + * This key is used to retrieve or map the entity within a broader data structure. + *

    + * + * @return The key of the JSON object representing this entity. + * @throws IllegalStateException if the key is unexpectedly {@code null}. + * @author MeAlam + * @since 1.0.0 + */ + public String getJsonKey() { + if (this.jsonKey == null) { + Throwable throwable = new Throwable("JSON key should not be null"); + IllegalStateException exception = new IllegalStateException("JSON key is unexpectedly null when retrieving from VariantParameter."); + BaseLogger.log(BaseLogLevel.ERROR, "JSON key is unexpectedly null when retrieving from VariantParameter.", throwable, true); + throw exception; + } + BaseLogger.log(BaseLogLevel.INFO, "Retrieved JSON key: " + this.jsonKey, true); + return this.jsonKey; + } + + /** + * A {@link String} method that retrieves the name of the variant parameter. + *

    + * The variant name is, by default, stored under the key {@code "variantName"} in the parameters/JSON files. + * Otherwise, the key is stored in the {@link #variantParameterName} field. + *

    + * + * @return The name of the variant, or {@code null} if the variant name is not found. + * @author MeAlam + * @since 1.0.0 + */ + public String getVariantParameter() { + String variantName = getParameter(variantParameterName); + BaseLogger.log(BaseLogLevel.INFO, "Retrieved parameter name: " + variantName, true); + return variantName; + } + + /** + * A {@link String} method that sets the name of the variant parameter. + *

    + * This method allows the user to customize the key used to locate the variant name within the parameters/JSON files. + *

    + * + * @param pCustomVariantName {@link String} - The custom name of the variant parameter. + * @author MeAlam + * @since 1.0.0 + */ + public void setVariantParameter(String pCustomVariantName) { + variantParameterName = pCustomVariantName; + BaseLogger.log(BaseLogLevel.INFO, "Setting parameter name: " + variantParameterName, true); + } + + /** + * A {@link String} method that retrieves the value of a specific parameter by its key. + *

    + * This method looks up the parameter's value within the internal data structure. + *

    + * + * @param pKey {@link String} - The key of the parameter to retrieve. + * @return The value of the parameter, or {@code null} if the key does not exist. + * @author MeAlam + * @since 1.0.0 + */ + public String getParameter(String pKey) { + String value = (String) super.getParameter(pKey); + BaseLogger.log(BaseLogLevel.INFO, "Retrieved parameter for key " + pKey + ": " + value, true); + return value; + } +} diff --git a/neoforge/src/main/java/software/bluelib/entity/variant/base/ParameterBase.java b/neoforge/src/main/java/software/bluelib/entity/variant/base/ParameterBase.java new file mode 100644 index 00000000..8e62626e --- /dev/null +++ b/neoforge/src/main/java/software/bluelib/entity/variant/base/ParameterBase.java @@ -0,0 +1,211 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.entity.variant.base; + +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * A {@code public abstract base class} for managing a collection of {@link #parameters}. + *

    + * This {@code class} provides methods to add, retrieve, remove, and manipulate {@link #parameters} stored as key-value pairs. + *

    + * Key Methods: + *
      + *
    • {@link #addParameter(String, Object)} - Adds a parameter to {@link #parameters}.
    • + *
    • {@link #getParameter(String)} - Retrieves a parameter from {@link #parameters}.
    • + *
    • {@link #removeParameter(String)} - Removes a parameter from {@link #parameters}.
    • + *
    • {@link #getAllParameters()} - Returns all parameters in {@link #parameters}.
    • + *
    • {@link #containsParameter(String)} - Checks if a parameter exists by its key from {@link #parameters}.
    • + *
    • {@link #isEmpty()} - Checks if {@link #parameters} is empty.
    • + *
    • {@link #clearParameters()} - Clears all parameters from {@link #parameters}.
    • + *
    • {@link #getParameterCount()} - Returns the number of parameters in {@link #parameters}.
    • + *
    • {@link #getParameterKeys()} - Returns a set of all parameter keys from {@link #parameters}.
    • + *
    • {@link #getParameterValues()} - Returns a collection of all parameter values from {@link #parameters}.
    • + *
    • {@link #updateParameter(String, Object)} - Updates the value of an existing parameter in {@link #parameters}.
    • + *
    + * + * @author MeAlam + * @since 1.0.0 + */ +public abstract class ParameterBase { + + /** + * A {@code private final} {@link Map} to store parameters as key-value pairs. + *

    + * This {@link Map} holds parameter keys and their corresponding values. + *

    + * + * @since 1.0.0 + */ + private final Map parameters = new HashMap<>(); + + /** + * A {@code protected void} that adds a parameter to {@link #parameters}. + *

    + * This method stores a new parameter with the specified key and value in {@link #parameters}. + *

    + * + * @param pKey {@link String} - The key under which the parameter is stored. + * @param pValue {@link Object} - The value of the parameter. + * @author MeAlam + * @since 1.0.0 + */ + protected void addParameter(String pKey, Object pValue) { + parameters.put(pKey, pValue); + } + + /** + * A {@code protected} {@link Object} that retrieves a parameter from {@link #parameters} by its key. + *

    + * This method returns the value associated with the specified key, or {@code null} if the key does not exist. + *

    + * + * @param pKey {@link String} - The key of the parameter to retrieve. + * @return {@link Object} - The value associated with the key, or {@code null} if the key does not exist. + * @author MeAlam + * @since 1.0.0 + */ + protected Object getParameter(String pKey) { + return parameters.get(pKey); + } + + /** + * A {@code protected void} that removes a parameter from {@link #parameters} by its key. + *

    + * This method deletes the parameter with the specified key from {@link #parameters}. If the key does not exist, no action is taken. + *

    + * + * @param pKey {@link String} - The key of the parameter to remove. + * @author MeAlam + * @since 1.0.0 + */ + protected void removeParameter(String pKey) { + if (parameters.remove(pKey) != null) { + BaseLogger.log(BaseLogLevel.SUCCESS, String.format("Parameter removed: Key = %s", pKey), true); + } else { + BaseLogger.log(BaseLogLevel.WARNING, String.format("Attempted to remove non-existent parameter: Key = %s", pKey), true); + } + } + + /** + * A {@code protected} {@link Map} that returns all parameters in {@link #parameters}. + *

    + * This method returns a new {@link Map} containing all parameters stored in {@link #parameters}. + *

    + * + * @return {@link Map} - A {@link Map} containing all parameters. + * @author MeAlam + * @since 1.0.0 + */ + protected Map getAllParameters() { + return new HashMap<>(parameters); + } + + /** + * A {@code protected} {@link Boolean} that checks if a parameter exists by its key. + *

    + * This method returns {@code true} if the parameter with the specified key exists in {@link #parameters}, {@code false} otherwise. + *

    + * + * @param pKey {@link String} - The key of the parameter to check. + * @return {@link Boolean} - {@code true} if the parameter exists and {@code false} if it doesn't. + * @author MeAlam + * @since 1.0.0 + */ + protected boolean containsParameter(String pKey) { + return parameters.containsKey(pKey); + } + + /** + * A {@code protected} {@link Boolean} that checks if {@link #parameters} is empty. + *

    + * This method returns {@code true} if {@link #parameters} contains no parameters, {@code false} otherwise. + *

    + * + * @return {@link Boolean} - {@code true} if {@link #parameters} is empty and {@code false} if it isn't. + * @author MeAlam + * @since 1.0.0 + */ + protected boolean isEmpty() { + return parameters.isEmpty(); + } + + /** + * A {@code protected void} that removes all parameters from {@link #parameters}. + * + * @author MeAlam + * @since 1.0.0 + */ + protected void clearParameters() { + parameters.clear(); + } + + /** + * A {@code protected} {@link Integer} that returns the number of parameters in {@link #parameters}. + * + * @return {@link Integer} - The number of parameters in the collection. + * @author MeAlam + * @since 1.0.0 + */ + protected int getParameterCount() { + return parameters.size(); + } + + /** + * A {@code protected} {@link Set} that returns a set of all parameter keys. + *

    + * This method provides a {@link Set} containing all the keys of parameters in {@link #parameters}. + *

    + * + * @return {@link Set} - A {@link Set} containing all parameter keys. + * @author MeAlam + * @since 1.0.0 + */ + protected Set getParameterKeys() { + return parameters.keySet(); + } + + /** + * A {@code protected} {@link Collection} that returns a {@link Collection} of all parameter values. + *

    + * This method provides a {@link Collection} containing all the values of parameters in {@link #parameters}. + *

    + * + * @return {@link Collection} - A {@link Collection} containing all parameter values. + * @author MeAlam + * @since 1.0.0 + */ + protected Collection getParameterValues() { + return parameters.values(); + } + + /** + * A {@code protected void} that updates the value of an existing parameter. + *

    + * This method changes the value of a parameter in {@link #parameters} that is identified by the specified key. If the key does not exist, an exception is thrown. + *

    + * + * @param pKey {@link String} - The key of the parameter to update. + * @param pNewValue {@link Object} - The new value to set for the parameter. + * @throws IllegalArgumentException if the key does not exist. + * @author MeAlam + * @since 1.0.0 + */ + protected void updateParameter(String pKey, Object pNewValue) { + if (parameters.containsKey(pKey)) { + parameters.put(pKey, pNewValue); + BaseLogger.log(BaseLogLevel.SUCCESS, String.format("Parameter updated: Key = %s, New Value = %s", pKey, pNewValue), true); + } else { + Throwable throwable = new Throwable("Key does not exist: " + pKey); + IllegalArgumentException exception = new IllegalArgumentException("Key does not exist: " + pKey); + BaseLogger.log(BaseLogLevel.ERROR, String.format("Attempted to update non-existent parameter: Key = %s", pKey), throwable, true); + throw exception; + } + } +} diff --git a/neoforge/src/main/java/software/bluelib/event/ReloadEventHandler.java b/neoforge/src/main/java/software/bluelib/event/ReloadEventHandler.java new file mode 100644 index 00000000..490c730b --- /dev/null +++ b/neoforge/src/main/java/software/bluelib/event/ReloadEventHandler.java @@ -0,0 +1,79 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.event; + +import com.google.gson.JsonParseException; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import software.bluelib.entity.variant.VariantLoader; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +/** + * A {@code class} responsible for handling events related to reloading entity variants. + *

    + * This class provides functionality to register entity variants from specified locations when the server starts. + *

    + *

    + * Key Features: + *

      + *
    • {@link #registerEntityVariants(String, MinecraftServer, String, String)} - Registers entity variants from specified locations.
    • + *
    + * + * @author MeAlam + * @see VariantLoader + * @see MinecraftServer + * @see ResourceLocation + * @since 1.0.0 + */ +public class ReloadEventHandler { + + /** + * A {@code protected static void} that registers entity variants from specified locations. + *

    + * This method attempts to load variants from both mod and datapack locations. It logs status information and + * handles exceptions that occur during the loading process. + *

    + *

    + * Parameters: + *

      + *
    • {@code pFolderPath} {@link String} - The folder path location within the mod or datapack where variants are stored.
    • + *
    • {@code pServer} {@link MinecraftServer} - The server instance of the current world.
    • + *
    • {@code pModID} {@link String} - The mod ID used to locate the entity variant resources. (Use your Mod's ID)
    • + *
    • {@code pEntityName} {@link String} - The entity name to load.
    • + *
    + * + * Exception Handling: + *
      + *
    • {@link JsonParseException} - Thrown when there is an error parsing the JSON files.
    • + *
    • {@link RuntimeException} - Thrown for unexpected errors during the registration process.
    • + *
    + * + * @param pFolderPath {@link String} - The folder path location within the mod or datapack where variants are stored. + * @param pServer {@link MinecraftServer} - The server instance of the current world. + * @param pModID {@link String} - The mod ID used to locate the entity variant resources. (Use your Mod's ID) + * @param pEntityName {@link String} - The entity name to load. + * @throws JsonParseException if there is an error parsing the JSON files. + * @throws RuntimeException if an unexpected error occurs during the registration process. + * @author MeAlam + * @see MinecraftServer + * @see ResourceLocation + * @see VariantLoader + * @since 1.0.0 + */ + protected static void registerEntityVariants(String pFolderPath, MinecraftServer pServer, String pModID, String pEntityName) { + + BaseLogger.log(BaseLogLevel.INFO, "Attempting to register entity variants for " + pEntityName + " with ModID: " + pModID, true); + + try { + VariantLoader.loadVariants(pFolderPath, pServer, pEntityName); + BaseLogger.log(BaseLogLevel.SUCCESS, "Successfully registered entity variants for " + pEntityName + " from ModID: " + pModID, true); + } catch (JsonParseException pException) { + BaseLogger.log(BaseLogLevel.ERROR, "Failed to parse JSON(s) while registering entity variants for " + pEntityName + " from ModID: " + pModID, pException, true); + throw pException; + } catch (Exception pException) { + BaseLogger.log(BaseLogLevel.ERROR, "Unexpected error occurred while registering entity variants for " + pEntityName + " from ModID: " + pModID, pException, true); + throw pException; + } + } +} diff --git a/neoforge/src/main/java/software/bluelib/example/entity/dragon/DragonEntity.java b/neoforge/src/main/java/software/bluelib/example/entity/dragon/DragonEntity.java new file mode 100644 index 00000000..413ca41a --- /dev/null +++ b/neoforge/src/main/java/software/bluelib/example/entity/dragon/DragonEntity.java @@ -0,0 +1,251 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.example.entity.dragon; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.network.syncher.EntityDataSerializers; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.DifficultyInstance; +import net.minecraft.world.entity.*; +import net.minecraft.world.entity.ai.attributes.AttributeSupplier; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.ServerLevelAccessor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import software.bernie.geckolib.animatable.GeoEntity; +import software.bernie.geckolib.core.animatable.instance.AnimatableInstanceCache; +import software.bernie.geckolib.core.animation.AnimatableManager; +import software.bernie.geckolib.util.GeckoLibUtil; +import software.bluelib.interfaces.variant.IVariantEntity; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; +import software.bluelib.utils.variant.ParameterUtils; + +/** + * A {@code DragonEntity} class representing a dragon entity in the game, which extends {@link TamableAnimal} + * and implements {@link IVariantEntity} and {@link GeoEntity}. + *

    + * This class manages the dragon's variant system, its data synchronization, and integrates with the GeckoLib + * animation system. + *

    + * Key Methods: + *
      + *
    • {@link #defineSynchedData()} - Defines the synchronized data for the dragon entity, including its variant.
    • + *
    • {@link #addAdditionalSaveData(CompoundTag)} - Adds custom data to the entity's NBT for saving.
    • + *
    • {@link #readAdditionalSaveData(CompoundTag)} - Reads custom data from the entity's NBT for loading.
    • + *
    • {@link #finalizeSpawn(ServerLevelAccessor, DifficultyInstance, MobSpawnType, SpawnGroupData, CompoundTag)} - Finalizes the spawning process and sets up parameters.
    • + *
    • {@link #setVariantName(String)} - Sets the variant name of the dragon.
    • + *
    • {@link #getVariantName()} - Retrieves the current variant name of the dragon.
    • + *
    + * + * @author MeAlam + * @since 1.0.0 + */ +public class DragonEntity extends TamableAnimal implements IVariantEntity, GeoEntity { + /** + * Entity data accessor for the variant of the dragon. + *

    + * This is used to store and retrieve the variant data for synchronization between server and client. + *

    + * + * @since 1.0.0 + */ + public static final EntityDataAccessor VARIANT = SynchedEntityData.defineId(DragonEntity.class, EntityDataSerializers.STRING); + + /** + * The name of the entity. + * + * @since 1.0.0 + */ + protected final String entityName = "dragon"; + + /** + * Constructs a new {@link DragonEntity} instance with the specified entity type and level. + * + * @param pEntityType {@link EntityType} - The type of the entity. + * @param pLevel {@link Level} - The level in which the entity is created. + * @author MeAlam + * @since 1.0.0 + */ + public DragonEntity(EntityType pEntityType, Level pLevel) { + super(pEntityType, pLevel); + } + + /** + * Defines the synchronized data for this dragon entity, including the variant. + *

    + * This method initializes the {@link EntityDataAccessor} to handle the variant data. + *

    + * + * @author MeAlam + * @since 1.0.0 + */ + @Override + protected void defineSynchedData() { + super.defineSynchedData(); + this.entityData.define(VARIANT, "normal"); + } + + /** + * Adds custom data to the entity's NBT tag for saving. + *

    + * This method stores the variant name in the NBT data so it can be restored when loading the entity. + *

    + * + * @param pCompound {@link CompoundTag} - The NBT tag to which data should be added. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public void addAdditionalSaveData(@NotNull CompoundTag pCompound) { + super.addAdditionalSaveData(pCompound); + pCompound.putString("Variant", getVariantName()); + } + + /** + * Reads custom data from the entity's NBT tag for loading. + *

    + * This method retrieves the variant name from the NBT data and sets it for the entity. + *

    + * + * @param pCompound {@link CompoundTag} - The NBT tag from which data should be read. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public void readAdditionalSaveData(@NotNull CompoundTag pCompound) { + super.readAdditionalSaveData(pCompound); + this.setVariantName(pCompound.getString("Variant")); + } + + /** + * Finalizes the spawning of the dragon entity. + *

    + * This method sets up the variant for the entity and connects parameters if needed. + *

    + * + * @param pLevel {@link ServerLevelAccessor} - The level in which the entity is spawned. + * @param pDifficulty {@link DifficultyInstance} - The difficulty instance for spawning. + * @param pReason {@link MobSpawnType} - The reason for spawning the entity. + * @param pSpawnData {@link SpawnGroupData} - Data related to the spawn. + * @return {@link SpawnGroupData} - Updated spawn data. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public SpawnGroupData finalizeSpawn(@NotNull ServerLevelAccessor pLevel, @NotNull DifficultyInstance pDifficulty, @NotNull MobSpawnType pReason, @Nullable SpawnGroupData pSpawnData, @Nullable CompoundTag pSpawnTag) { + if (getVariantName() == null || getVariantName().isEmpty()) { + this.setVariantName(getRandomVariant(getEntityVariants(entityName), "normal")); + ParameterUtils.ParameterBuilder.forVariant(entityName, this.getVariantName()) + .withParameter("customParameter") + .withParameter("int") + .withParameter("bool") + .withParameter("array") + .connect(); + } + BaseLogger.log(BaseLogLevel.SUCCESS, "Dragon Spawned with Variant: " + getVariantName(), true); + return super.finalizeSpawn(pLevel, pDifficulty, pReason, pSpawnData, pSpawnTag); + } + + /** + * Sets the variant name for the dragon entity. + * + * @param pName {@link String} - The name of the variant to set. + * @author MeAlam + * @since 1.0.0 + */ + public void setVariantName(String pName) { + this.entityData.set(VARIANT, pName); + } + + /** + * Retrieves the current variant name of the dragon entity. + * + * @return {@link String} - The current variant name. + * @author MeAlam + * @since 1.0.0 + */ + public String getVariantName() { + return this.entityData.get(VARIANT); + } + /* All Code below this Fragment is not Library Related!!! */ + + /** + * The cache for the animatable instance. + * + * @since 1.0.0 + */ + private final AnimatableInstanceCache cache = GeckoLibUtil.createInstanceCache(this); + + /** + * Defines the synchronized data for the dragon entity. + * + * @return {@link SynchedEntityData} - The builder for the synchronized data. + * @author MeAlam + * @since 1.0.0 + */ + public static AttributeSupplier.Builder createAttributes() { + return Mob.createMobAttributes() + .add(Attributes.MOVEMENT_SPEED, 0.3) + .add(Attributes.MAX_HEALTH, 10) + .add(Attributes.ARMOR, 0) + .add(Attributes.ATTACK_DAMAGE, 3) + .add(Attributes.FOLLOW_RANGE, 16) + .add(Attributes.FLYING_SPEED, 0.3); + } + + /** + * Adds custom data to the entity's NBT for saving. + * + * @param pControllerRegistrar {@link CompoundTag} - The tag to add the data to. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public void registerControllers(AnimatableManager.ControllerRegistrar pControllerRegistrar) { + } + + /** + * Adds custom data to the entity's NBT for saving. + * + * @return {@link CompoundTag} - The tag with the custom data. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public AnimatableInstanceCache getAnimatableInstanceCache() { + return cache; + } + + /** + * Adds custom data to the entity's NBT for saving. + * + * @param pLevel {@link CompoundTag} - The tag to add the data to. + * @param pOtherParent {@link CompoundTag} - The other tag to add the data from. + * @return {@link CompoundTag} - The tag with the custom data. + * @author MeAlam + * @since 1.0.0 + */ + @Nullable + @Override + public AgeableMob getBreedOffspring(@NotNull ServerLevel pLevel, @NotNull AgeableMob pOtherParent) { + return null; + } + + /** + * Adds custom data to the entity's NBT for saving. + * + * @param pItemStack {@link ItemStack} - The item stack to check. + * @return {@link boolean} - Whether the item is food or not. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public boolean isFood(@NotNull ItemStack pItemStack) { + return false; + } +} diff --git a/neoforge/src/main/java/software/bluelib/example/entity/dragon/DragonModel.java b/neoforge/src/main/java/software/bluelib/example/entity/dragon/DragonModel.java new file mode 100644 index 00000000..d5f85626 --- /dev/null +++ b/neoforge/src/main/java/software/bluelib/example/entity/dragon/DragonModel.java @@ -0,0 +1,59 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.example.entity.dragon; + +import net.minecraft.resources.ResourceLocation; +import software.bernie.geckolib.model.GeoModel; +import software.bluelib.BlueLibConstants; + +/** + * A {@code public class} that extends {@link GeoModel} for the {@link DragonEntity} entity. + * Key Methods: + *
      + *
    • {@link #getModelResource(DragonEntity)} - Get the Model Location.
    • + *
    • {@link #getTextureResource(DragonEntity)} - Get the Texture Location.
    • + *
    • {@link #getAnimationResource(DragonEntity)} - Get the Animation Location.
    • + *
    + */ +public class DragonModel extends GeoModel { + + + /** + * A {@code public} {@link ResourceLocation} method that returns the location of the model. + * + * @param pObject {@link DragonEntity} - The entity to get the model for. + * @return {@link ResourceLocation} - The location of the model. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public ResourceLocation getModelResource(DragonEntity pObject) { + return new ResourceLocation(BlueLibConstants.MOD_ID, "geo/dragon.geo.json"); + } + + /** + * A {@code public} {@link ResourceLocation} method that returns the location of the texture. + * + * @param pObject {@link DragonEntity} - The entity to get the texture for. + * @return {@link ResourceLocation} - The location of the texture. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public ResourceLocation getTextureResource(DragonEntity pObject) { + return pObject.getTextureLocation(BlueLibConstants.MOD_ID, "textures/entity/" + pObject.entityName + "/" + pObject.getVariantName() + ".png"); + } + + /** + * A {@code public} {@link ResourceLocation} method that returns the location of the animation. + * + * @param pAnimatable {@link DragonEntity} - The entity to get the animation for. + * @return {@link ResourceLocation} - The location of the animation. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public ResourceLocation getAnimationResource(DragonEntity pAnimatable) { + return new ResourceLocation(BlueLibConstants.MOD_ID, "animations/dragon.animation.json"); + } +} diff --git a/neoforge/src/main/java/software/bluelib/example/entity/dragon/DragonRender.java b/neoforge/src/main/java/software/bluelib/example/entity/dragon/DragonRender.java new file mode 100644 index 00000000..d6a4e340 --- /dev/null +++ b/neoforge/src/main/java/software/bluelib/example/entity/dragon/DragonRender.java @@ -0,0 +1,26 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.example.entity.dragon; + +import net.minecraft.client.renderer.entity.EntityRendererProvider; +import software.bernie.geckolib.renderer.GeoEntityRenderer; + +/** + * A {@code public class} that extends {@link GeoEntityRenderer} for rendering the dragon entity. + * + * @author MeAlam + * @since 1.0.0 + */ +public class DragonRender extends GeoEntityRenderer { + + /** + * Constructor + * + * @param pRenderManager {@link EntityRendererProvider.Context} - The render manager. + * @author MeAlam + * @since 1.0.0 + */ + public DragonRender(EntityRendererProvider.Context pRenderManager) { + super(pRenderManager, new DragonModel()); + } +} diff --git a/neoforge/src/main/java/software/bluelib/example/entity/rex/RexEntity.java b/neoforge/src/main/java/software/bluelib/example/entity/rex/RexEntity.java new file mode 100644 index 00000000..270e9025 --- /dev/null +++ b/neoforge/src/main/java/software/bluelib/example/entity/rex/RexEntity.java @@ -0,0 +1,251 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.example.entity.rex; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.network.syncher.EntityDataSerializers; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.DifficultyInstance; +import net.minecraft.world.entity.*; +import net.minecraft.world.entity.ai.attributes.AttributeSupplier; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.ServerLevelAccessor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import software.bernie.geckolib.animatable.GeoEntity; +import software.bernie.geckolib.core.animatable.instance.AnimatableInstanceCache; +import software.bernie.geckolib.core.animation.AnimatableManager; +import software.bernie.geckolib.util.GeckoLibUtil; +import software.bluelib.interfaces.variant.IVariantEntity; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; +import software.bluelib.utils.variant.ParameterUtils; + +/** + * A {@code rexEntity} class representing a rex entity in the game, which extends {@link TamableAnimal} + * and implements {@link IVariantEntity} and {@link GeoEntity}. + *

    + * This class manages the rex's variant system, its data synchronization, and integrates with the GeckoLib + * animation system. + *

    + * Key Methods: + *
      + *
    • {@link #defineSynchedData()} - Defines the synchronized data for the rex entity, including its variant.
    • + *
    • {@link #addAdditionalSaveData(CompoundTag)} - Adds custom data to the entity's NBT for saving.
    • + *
    • {@link #readAdditionalSaveData(CompoundTag)} - Reads custom data from the entity's NBT for loading.
    • + *
    • {@link #finalizeSpawn(ServerLevelAccessor, DifficultyInstance, MobSpawnType, SpawnGroupData, CompoundTag)} - Finalizes the spawning process and sets up parameters.
    • + *
    • {@link #setVariantName(String)} - Sets the variant name of the rex.
    • + *
    • {@link #getVariantName()} - Retrieves the current variant name of the rex.
    • + *
    + * + * @author MeAlam + * @since 1.0.0 + */ +public class RexEntity extends TamableAnimal implements IVariantEntity, GeoEntity { + /** + * Entity data accessor for the variant of the rex. + *

    + * This is used to store and retrieve the variant data for synchronization between server and client. + *

    + * + * @since 1.0.0 + */ + public static final EntityDataAccessor VARIANT = SynchedEntityData.defineId(RexEntity.class, EntityDataSerializers.STRING); + + /** + * The name of the entity. + * + * @since 1.0.0 + */ + protected final String entityName = "rex"; + + /** + * Constructs a new {@link RexEntity} instance with the specified entity type and level. + * + * @param pEntityType {@link EntityType} - The type of the entity. + * @param pLevel {@link Level} - The level in which the entity is created. + * @author MeAlam + * @since 1.0.0 + */ + public RexEntity(EntityType pEntityType, Level pLevel) { + super(pEntityType, pLevel); + } + + /** + * Defines the synchronized data for this rex entity, including the variant. + *

    + * This method initializes the {@link EntityDataAccessor} to handle the variant data. + *

    + * + * @author MeAlam + * @since 1.0.0 + */ + @Override + protected void defineSynchedData() { + super.defineSynchedData(); + this.entityData.define(VARIANT, "normal"); + } + + /** + * Adds custom data to the entity's NBT tag for saving. + *

    + * This method stores the variant name in the NBT data so it can be restored when loading the entity. + *

    + * + * @param pCompound {@link CompoundTag} - The NBT tag to which data should be added. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public void addAdditionalSaveData(@NotNull CompoundTag pCompound) { + super.addAdditionalSaveData(pCompound); + pCompound.putString("Variant", getVariantName()); + } + + /** + * Reads custom data from the entity's NBT tag for loading. + *

    + * This method retrieves the variant name from the NBT data and sets it for the entity. + *

    + * + * @param pCompound {@link CompoundTag} - The NBT tag from which data should be read. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public void readAdditionalSaveData(@NotNull CompoundTag pCompound) { + super.readAdditionalSaveData(pCompound); + this.setVariantName(pCompound.getString("Variant")); + } + + /** + * Finalizes the spawning of the rex entity. + *

    + * This method sets up the variant for the entity and connects parameters if needed. + *

    + * + * @param pLevel {@link ServerLevelAccessor} - The level in which the entity is spawned. + * @param pDifficulty {@link DifficultyInstance} - The difficulty instance for spawning. + * @param pReason {@link MobSpawnType} - The reason for spawning the entity. + * @param pSpawnData {@link SpawnGroupData} - Data related to the spawn. + * @return {@link SpawnGroupData} - Updated spawn data. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public SpawnGroupData finalizeSpawn(@NotNull ServerLevelAccessor pLevel, @NotNull DifficultyInstance pDifficulty, @NotNull MobSpawnType pReason, @Nullable SpawnGroupData pSpawnData, @Nullable CompoundTag pSpawnTag) { + if (getVariantName() == null || getVariantName().isEmpty()) { + this.setVariantName(getRandomVariant(getEntityVariants(entityName), "normal")); + ParameterUtils.ParameterBuilder.forVariant(entityName, this.getVariantName()) + .withParameter("customParameter") + .withParameter("int") + .withParameter("bool") + .withParameter("array") + .connect(); + } + BaseLogger.log(BaseLogLevel.SUCCESS, "Rex Spawned with Variant: " + getVariantName(), true); + return super.finalizeSpawn(pLevel, pDifficulty, pReason, pSpawnData, pSpawnTag); + } + + /** + * Sets the variant name for the rex entity. + * + * @param pName {@link String} - The name of the variant to set. + * @author MeAlam + * @since 1.0.0 + */ + public void setVariantName(String pName) { + this.entityData.set(VARIANT, pName); + } + + /** + * Retrieves the current variant name of the rex entity. + * + * @return {@link String} - The current variant name. + * @author MeAlam + * @since 1.0.0 + */ + public String getVariantName() { + return this.entityData.get(VARIANT); + } + /* All Code below this Fragment is not Library Related!!! */ + + /** + * The cache for the animatable instance. + * + * @since 1.0.0 + */ + private final AnimatableInstanceCache cache = GeckoLibUtil.createInstanceCache(this); + + /** + * Defines the synchronized data for the rex entity. + * + * @return {@link SynchedEntityData} - The builder for the synchronized data. + * @author MeAlam + * @since 1.0.0 + */ + public static AttributeSupplier.Builder createAttributes() { + return Mob.createMobAttributes() + .add(Attributes.MOVEMENT_SPEED, 0.3) + .add(Attributes.MAX_HEALTH, 10) + .add(Attributes.ARMOR, 0) + .add(Attributes.ATTACK_DAMAGE, 3) + .add(Attributes.FOLLOW_RANGE, 16) + .add(Attributes.FLYING_SPEED, 0.3); + } + + /** + * Adds custom data to the entity's NBT for saving. + * + * @param pControllerRegistrar {@link CompoundTag} - The tag to add the data to. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public void registerControllers(AnimatableManager.ControllerRegistrar pControllerRegistrar) { + } + + /** + * Adds custom data to the entity's NBT for saving. + * + * @return {@link CompoundTag} - The tag with the custom data. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public AnimatableInstanceCache getAnimatableInstanceCache() { + return cache; + } + + /** + * Adds custom data to the entity's NBT for saving. + * + * @param pLevel {@link CompoundTag} - The tag to add the data to. + * @param pOtherParent {@link CompoundTag} - The other tag to add the data from. + * @return {@link CompoundTag} - The tag with the custom data. + * @author MeAlam + * @since 1.0.0 + */ + @Nullable + @Override + public AgeableMob getBreedOffspring(@NotNull ServerLevel pLevel, @NotNull AgeableMob pOtherParent) { + return null; + } + + /** + * Adds custom data to the entity's NBT for saving. + * + * @param pItemStack {@link ItemStack} - The item stack to check. + * @return {@link boolean} - Whether the item is food or not. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public boolean isFood(@NotNull ItemStack pItemStack) { + return false; + } +} diff --git a/neoforge/src/main/java/software/bluelib/example/entity/rex/RexModel.java b/neoforge/src/main/java/software/bluelib/example/entity/rex/RexModel.java new file mode 100644 index 00000000..f408313b --- /dev/null +++ b/neoforge/src/main/java/software/bluelib/example/entity/rex/RexModel.java @@ -0,0 +1,59 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.example.entity.rex; + +import net.minecraft.resources.ResourceLocation; +import software.bernie.geckolib.model.GeoModel; +import software.bluelib.BlueLibConstants; + +/** + * A {@code public class} that extends {@link GeoModel} for the {@link RexEntity} entity. + * Key Methods: + *
      + *
    • {@link #getModelResource(RexEntity)} - Get the Model Location.
    • + *
    • {@link #getTextureResource(RexEntity)} - Get the Texture Location.
    • + *
    • {@link #getAnimationResource(RexEntity)} - Get the Animation Location.
    • + *
    + */ +public class RexModel extends GeoModel { + + + /** + * A {@code public} {@link ResourceLocation} method that returns the location of the model. + * + * @param pObject {@link RexEntity} - The entity to get the model for. + * @return {@link ResourceLocation} - The location of the model. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public ResourceLocation getModelResource(RexEntity pObject) { + return new ResourceLocation(BlueLibConstants.MOD_ID, "geo/rex.geo.json"); + } + + /** + * A {@code public} {@link ResourceLocation} method that returns the location of the texture. + * + * @param pObject {@link RexEntity} - The entity to get the texture for. + * @return {@link ResourceLocation} - The location of the texture. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public ResourceLocation getTextureResource(RexEntity pObject) { + return pObject.getTextureLocation(BlueLibConstants.MOD_ID, "textures/entity/" + pObject.entityName + "/" + pObject.getVariantName() + ".png"); + } + + /** + * A {@code public} {@link ResourceLocation} method that returns the location of the animation. + * + * @param pAnimatable {@link RexEntity} - The entity to get the animation for. + * @return {@link ResourceLocation} - The location of the animation. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public ResourceLocation getAnimationResource(RexEntity pAnimatable) { + return new ResourceLocation(BlueLibConstants.MOD_ID, "animations/rex.animation.json"); + } +} diff --git a/neoforge/src/main/java/software/bluelib/example/entity/rex/RexRender.java b/neoforge/src/main/java/software/bluelib/example/entity/rex/RexRender.java new file mode 100644 index 00000000..2ca92fae --- /dev/null +++ b/neoforge/src/main/java/software/bluelib/example/entity/rex/RexRender.java @@ -0,0 +1,26 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.example.entity.rex; + +import net.minecraft.client.renderer.entity.EntityRendererProvider; +import software.bernie.geckolib.renderer.GeoEntityRenderer; + +/** + * A {@code public class} that extends {@link GeoEntityRenderer} for rendering the rex entity. + * + * @author MeAlam + * @since 1.0.0 + */ +public class RexRender extends GeoEntityRenderer { + + /** + * Constructor + * + * @param pRenderManager {@link EntityRendererProvider.Context} - The render manager. + * @author MeAlam + * @since 1.0.0 + */ + public RexRender(EntityRendererProvider.Context pRenderManager) { + super(pRenderManager, new RexModel()); + } +} diff --git a/neoforge/src/main/java/software/bluelib/example/event/ClientEvents.java b/neoforge/src/main/java/software/bluelib/example/event/ClientEvents.java new file mode 100644 index 00000000..ff44b592 --- /dev/null +++ b/neoforge/src/main/java/software/bluelib/example/event/ClientEvents.java @@ -0,0 +1,57 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.example.event; + +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.neoforge.client.event.EntityRenderersEvent; +import net.neoforged.neoforge.event.entity.EntityAttributeCreationEvent; +import software.bluelib.example.entity.dragon.DragonEntity; +import software.bluelib.example.entity.dragon.DragonRender; +import software.bluelib.example.entity.rex.RexEntity; +import software.bluelib.example.entity.rex.RexRender; +import software.bluelib.example.init.ModEntities; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +/** + * A {@code public class} that contains the events that are fired on the client side. + *

    + * Key Methods: + *

      + *
    • {@link #registerRenderers(EntityRenderersEvent.RegisterRenderers)} - Registers the renderers for the entities.
    • + *
    • {@link #registerAttributes(EntityAttributeCreationEvent)} - Registers the attributes for the entities.
    • + *
    + * + * @author MeAlam + * @since 1.0.0 + */ +public class ClientEvents { + + /** + * A {@code public static void} that registers the renderers for the entities. + * + * @param pEvent {@link EntityRenderersEvent} - The event that is fired when the renderers are being registered. + * @author MeAlam + * @since 1.0.0 + */ + @SubscribeEvent + public static void registerRenderers(final EntityRenderersEvent.RegisterRenderers pEvent) { + pEvent.registerEntityRenderer(ModEntities.DRAGON.get(), DragonRender::new); + pEvent.registerEntityRenderer(ModEntities.REX.get(), RexRender::new); + BaseLogger.log(BaseLogLevel.INFO, "Registered Renderers for Entities", true); + } + + /** + * A {@code public static void} that registers the attributes for the entities. + * + * @param pEvent {@link EntityAttributeCreationEvent} - The event that is fired when the attributes are being registered. + * @author MeAlam + * @since 1.0.0 + */ + @SubscribeEvent + public static void registerAttributes(EntityAttributeCreationEvent pEvent) { + pEvent.put(ModEntities.DRAGON.get(), DragonEntity.createAttributes().build()); + pEvent.put(ModEntities.REX.get(), RexEntity.createAttributes().build()); + BaseLogger.log(BaseLogLevel.INFO, "Registered Attributes for Entities", true); + } +} diff --git a/NeoForge/src/main/java/software/bluelib/example/event/ReloadHandler.java b/neoforge/src/main/java/software/bluelib/example/event/ReloadHandler.java similarity index 80% rename from NeoForge/src/main/java/software/bluelib/example/event/ReloadHandler.java rename to neoforge/src/main/java/software/bluelib/example/event/ReloadHandler.java index 5c6bacca..e3a4f73d 100644 --- a/NeoForge/src/main/java/software/bluelib/example/event/ReloadHandler.java +++ b/neoforge/src/main/java/software/bluelib/example/event/ReloadHandler.java @@ -7,13 +7,13 @@ import net.neoforged.fml.common.Mod; import net.neoforged.neoforge.event.AddReloadListenerEvent; import net.neoforged.neoforge.event.server.ServerStartingEvent; -import software.bluelib.BlueLib; +import software.bluelib.BlueLibConstants; import software.bluelib.event.ReloadEventHandler; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; import java.util.Arrays; import java.util.List; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** @@ -23,20 +23,17 @@ * ensuring that entity variant data is properly loaded and refreshed. *

    * - *

    * Key Methods: *

      *
    • {@link #onServerStart(ServerStartingEvent)} - Handles server starting events to initialize entity variants.
    • *
    • {@link #onReload(AddReloadListenerEvent)} - Handles reload events to refresh entity variants.
    • *
    • {@link #LoadEntityVariants(MinecraftServer)} - Loads entity variants from JSON files into the server.
    • *
    - *

    * - * @since 1.0.0 * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ -@Mod.EventBusSubscriber +@Mod.EventBusSubscriber(modid = BlueLibConstants.MOD_ID) public class ReloadHandler extends ReloadEventHandler { /** @@ -46,7 +43,6 @@ public class ReloadHandler extends ReloadEventHandler { *

    * * @since 1.0.0 - * @Co-author MeAlam, Dan */ private static MinecraftServer server; @@ -55,25 +51,16 @@ public class ReloadHandler extends ReloadEventHandler { * and load entity variants. * * @param pEvent {@link ServerStartingEvent} - The event triggered when the server starts. - * - * @since 1.0.0 * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ @SubscribeEvent public static void onServerStart(ServerStartingEvent pEvent) { server = pEvent.getServer(); ReloadHandler.LoadEntityVariants(server); + BaseLogger.log(BaseLogLevel.INFO, "Entity variants loaded.", true); } - /** - * The {@link ScheduledExecutorService} used to schedule tasks for reloading entity variants. - * - * @since 1.0.0 - * @Co-author MeAlam, Dan - */ - private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); - /** * Handles the reload event by scheduling a task to reload entity variants. *

    @@ -81,17 +68,16 @@ public static void onServerStart(ServerStartingEvent pEvent) { *

    * * @param pEvent {@link AddReloadListenerEvent} - The event triggered when a reload occurs. - * - * @since 1.0.0 * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ @SubscribeEvent public static void onReload(AddReloadListenerEvent pEvent) { if (server != null) { - scheduler.schedule(() -> { + BlueLibConstants.SCHEDULER.schedule(() -> { server.execute(() -> { ReloadHandler.LoadEntityVariants(server); + BaseLogger.log(BaseLogLevel.INFO, "Entity variants reloaded.", true); }); }, 1, TimeUnit.SECONDS); } @@ -104,7 +90,6 @@ public static void onReload(AddReloadListenerEvent pEvent) { *

    * * @since 1.0.0 - * @Co-author MeAlam, Dan */ private static final String basePath = "variant/entity/"; @@ -115,7 +100,6 @@ public static void onReload(AddReloadListenerEvent pEvent) { *

    * * @since 1.0.0 - * @Co-author MeAlam, Dan */ private static final List entityNames = Arrays.asList("dragon", "rex"); @@ -127,16 +111,14 @@ public static void onReload(AddReloadListenerEvent pEvent) { *

    * * @param pServer {@link MinecraftServer} - The server on which the entity variants will be loaded. - * - * @since 1.0.0 * @author MeAlam - * @Co-author Dan + * @since 1.0.0 */ public static void LoadEntityVariants(MinecraftServer pServer) { for (String entityName : entityNames) { - String modPath = basePath + entityName + ".json"; - String dataPath = basePath + entityName + "data.json"; - ReloadEventHandler.registerEntityVariants(pServer, entityName, BlueLib.MODID, modPath, dataPath); + String folderPath = basePath + entityName; + ReloadEventHandler.registerEntityVariants(folderPath, pServer, BlueLibConstants.MOD_ID, entityName); + BaseLogger.log(BaseLogLevel.INFO, "Entity variants loaded for " + entityName + ".", true); } } } diff --git a/NeoForge/src/main/java/software/bluelib/example/init/ModEntities.java b/neoforge/src/main/java/software/bluelib/example/init/ModEntities.java similarity index 54% rename from NeoForge/src/main/java/software/bluelib/example/init/ModEntities.java rename to neoforge/src/main/java/software/bluelib/example/init/ModEntities.java index a1e80d1d..9f79c471 100644 --- a/NeoForge/src/main/java/software/bluelib/example/init/ModEntities.java +++ b/neoforge/src/main/java/software/bluelib/example/init/ModEntities.java @@ -8,14 +8,32 @@ import net.minecraft.world.entity.MobCategory; import net.neoforged.neoforge.registries.DeferredHolder; import net.neoforged.neoforge.registries.DeferredRegister; -import software.bluelib.BlueLib; +import software.bluelib.BlueLibConstants; import software.bluelib.example.entity.dragon.DragonEntity; import software.bluelib.example.entity.rex.RexEntity; +/** + * A {@code public class} for registering {@link EntityType} for this mod. + *

    + * Key Methods: + *

      + *
    • {@link #register(String, EntityType.Builder)} - Registers an {@link EntityType} with the specified {@code pRegistryName} and {@code pEntityTypeBuilder}.
    • + *
    + */ public class ModEntities { - public static final DeferredRegister> REGISTRY = DeferredRegister.create(Registries.ENTITY_TYPE, BlueLib.MODID); - // List of Entities + /** + * A {@code public static final} {@link DeferredRegister} of {@link EntityType} for this mod. + * + * @since 1.0.0 + */ + public static final DeferredRegister> REGISTRY = DeferredRegister.create(Registries.ENTITY_TYPE, BlueLibConstants.MOD_ID); + + /** + * A {@code public static final} {@link DeferredHolder} of {@link EntityType} for the {@link DragonEntity}. + * + * @since 1.0.0 + */ public static final DeferredHolder, EntityType> DRAGON = register( "example_one", EntityType.Builder.of(DragonEntity::new, MobCategory.AMBIENT) @@ -25,6 +43,11 @@ public class ModEntities { .fireImmune() .sized(0.6f, 1.8f)); + /** + * A {@code public static final} {@link DeferredHolder} of {@link EntityType} for the {@link RexEntity}. + * + * @since 1.0.0 + */ public static final DeferredHolder, EntityType> REX = register( "example_two", EntityType.Builder.of(RexEntity::new, MobCategory.AMBIENT) @@ -34,6 +57,16 @@ public class ModEntities { .fireImmune() .sized(0.6f, 1.8f)); + /** + * A {@code private static} {@link Entity} method to register an {@link EntityType} with the specified {@code pRegistryName} and {@code pEntityTypeBuilder}. + * + * @param pRegistryName {@link String} - The registry name of the {@link EntityType}. + * @param pEntityTypeBuilder {@link EntityType.Builder} - The builder of the {@link EntityType}. + * @param The type of the entity. + * @return {@link DeferredHolder} - The deferred holder of the {@link EntityType}. + * @author MeAlam + * @since 1.0.0 + */ private static DeferredHolder, EntityType> register(String pRegistryName, EntityType.Builder pEntityTypeBuilder) { return REGISTRY.register(pRegistryName, () -> pEntityTypeBuilder.build(pRegistryName)); } diff --git a/neoforge/src/main/java/software/bluelib/interfaces/variant/IVariantEntity.java b/neoforge/src/main/java/software/bluelib/interfaces/variant/IVariantEntity.java new file mode 100644 index 00000000..40f83867 --- /dev/null +++ b/neoforge/src/main/java/software/bluelib/interfaces/variant/IVariantEntity.java @@ -0,0 +1,59 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.interfaces.variant; + +import net.minecraft.util.RandomSource; +import software.bluelib.interfaces.variant.base.IVariantEntityBase; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +import java.util.List; + +/** + * A {@code public Interface} representing an entity that supports multiple variants. + *

    + * This interface extends {@link IVariantEntityBase} to include methods specific to handling entity variants, including + * random selection of variants. + *

    + *

    + * Key Methods: + *

      + *
    • {@link #getRandomVariant(List, String)} - Retrieves a random variant name from a provided list or defaults if the list is empty.
    • + *
    + * + * @author MeAlam + * @since 1.0.0 + */ +public interface IVariantEntity extends IVariantEntityBase { + + /** + * A {@link RandomSource} instance used for generating random variants. + * + * @since 1.0.0 + */ + RandomSource random = RandomSource.create(); + + /** + * A {@code default} {@link String} that selects a random variant name from the provided list of variant names. + *

    + * This method uses the {@link RandomSource} to pick a random variant from the list. If the list is empty, the default + * variant name is returned. + *

    + * + * @param pVariantNamesList {@link List} - A {@link List} of variant names available for the entity. + * @param pDefaultVariant {@link String} - The default variant name to return if {@code pVariantNamesList} is empty. + * @return A random variant name from the list, or the default variant if the list is empty. + * @author MeAlam + * @since 1.0.0 + */ + default String getRandomVariant(List pVariantNamesList, String pDefaultVariant) { + if (pVariantNamesList.isEmpty()) { + BaseLogger.log(BaseLogLevel.INFO, "Variant names list is empty. Returning default variant: " + pDefaultVariant, true); + return pDefaultVariant; + } + int index = random.nextInt(pVariantNamesList.size()); + String selectedVariant = pVariantNamesList.get(index); + BaseLogger.log(BaseLogLevel.SUCCESS, "Selected random variant: " + selectedVariant + " from list of size: " + pVariantNamesList.size(), true); + return selectedVariant; + } +} diff --git a/neoforge/src/main/java/software/bluelib/interfaces/variant/base/IVariantEntityBase.java b/neoforge/src/main/java/software/bluelib/interfaces/variant/base/IVariantEntityBase.java new file mode 100644 index 00000000..5019cf05 --- /dev/null +++ b/neoforge/src/main/java/software/bluelib/interfaces/variant/base/IVariantEntityBase.java @@ -0,0 +1,66 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.interfaces.variant.base; + +import net.minecraft.resources.ResourceLocation; +import software.bluelib.entity.variant.VariantLoader; +import software.bluelib.entity.variant.VariantParameter; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * A {@code public base Interface} providing fundamental methods for handling entity variants. + *

    + * This interface defines methods for retrieving texture locations and variant names associated with entities. + *

    + *

    + * Key Methods: + *

      + *
    • {@link #getTextureLocation(String, String)} - Retrieves the {@link ResourceLocation} for the entity texture.
    • + *
    • {@link #getEntityVariants(String)} - Retrieves a {@link List} of variant names for a specified entity.
    • + *
    + * + * @author MeAlam + * @since 1.0.0 + */ +public interface IVariantEntityBase { + + /** + * A {@code default} {@link ResourceLocation} that points to the texture of an entity. + *

    + * This method constructs a {@link ResourceLocation} using the provided mod ID and texture path. + *

    + * + * @param pModId {@link String} - The mod ID used to locate the texture. + * @param pPath {@link String} - The path to the texture within the mod. + * @return A {@link ResourceLocation} pointing to the specified texture. + * @author MeAlam + * @since 1.0.0 + */ + default ResourceLocation getTextureLocation(String pModId, String pPath) { + return new ResourceLocation(pModId, pPath); + } + + /** + * A {@code default} {@link List} of variant names associated with the specified entity. + *

    + * This method retrieves the names of all variants for a given entity by querying the {@link VariantLoader}. + *

    + * + * @param pEntityName {@link String} - The name of the entity whose variant names are to be retrieved. + * @return A {@link List} containing the names of variants associated with the specified entity. + * @author MeAlam + * @since 1.0.0 + */ + default List getEntityVariants(String pEntityName) { + List variants = VariantLoader.getVariantsFromEntity(pEntityName); + List variantNames = variants.stream() + .map(VariantParameter::getVariantParameter) + .collect(Collectors.toList()); + BaseLogger.log(BaseLogLevel.SUCCESS, "Retrieved " + variantNames.size() + " variants for entity: " + pEntityName, true); + return variantNames; + } +} diff --git a/neoforge/src/main/java/software/bluelib/json/JSONLoader.java b/neoforge/src/main/java/software/bluelib/json/JSONLoader.java new file mode 100644 index 00000000..6fa2d5b1 --- /dev/null +++ b/neoforge/src/main/java/software/bluelib/json/JSONLoader.java @@ -0,0 +1,73 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.json; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.Resource; +import net.minecraft.server.packs.resources.ResourceManager; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Optional; + +/** + * A {@code public class} responsible for loading and parsing JSON data from + * resources defined by {@link ResourceLocation} within a Minecraft mod environment.
    + * It uses the {@link Gson} library to convert JSON strings into {@link JsonObject} instances. + *

    + * Key Methods: + *

      + *
    • {@link #loadJson(ResourceLocation, ResourceManager)} - Loads a JSON resource from the specified location.
    • + *
    + * + * @author MeAlam + * @since 1.0.0 + */ +public class JSONLoader { + + /** + * A {@code private static} {@link Gson} instance for parsing JSON data. + */ + private static final Gson gson = new Gson(); + + /** + * A {@code public} {@link JsonObject} that loads JSON data from a {@link ResourceLocation}.
    + * This method is typically used to load configuration files or other JSON-based resources + * in a Minecraft mod environment. + * + * @param pResourceLocation {@link ResourceLocation} - The {@link ResourceLocation} of the JSON resource. + * @param pResourceManager {@link ResourceManager} - The {@link ResourceManager} used to load the resource. + * @return The loaded {@link JsonObject}. Returns an empty {@link JsonObject} if the resource is not found. + * @throws RuntimeException if there is an error reading the resource. + * @author MeAlam + * @since 1.0.0 + */ + public JsonObject loadJson(ResourceLocation pResourceLocation, ResourceManager pResourceManager) { + try { + Optional resource = pResourceManager.getResource(pResourceLocation); + + if (resource.isEmpty()) { + BaseLogger.log(BaseLogLevel.ERROR, "Resource not found: " + pResourceLocation, true); + return new JsonObject(); + } + + try (InputStream inputStream = resource.get().open(); + InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) { + + JsonObject jsonObject = gson.fromJson(reader, JsonObject.class); + BaseLogger.log(BaseLogLevel.SUCCESS, "Successfully loaded JSON resource: " + pResourceLocation, true); + return jsonObject; + } + } catch (IOException pException) { + RuntimeException exception = new RuntimeException("Failed to load JSON resource: " + pResourceLocation, pException); + BaseLogger.log(BaseLogLevel.ERROR, "Failed to load JSON resource: " + pResourceLocation, exception, true); + throw exception; + } + } +} diff --git a/neoforge/src/main/java/software/bluelib/json/JSONMerger.java b/neoforge/src/main/java/software/bluelib/json/JSONMerger.java new file mode 100644 index 00000000..e82cf1ff --- /dev/null +++ b/neoforge/src/main/java/software/bluelib/json/JSONMerger.java @@ -0,0 +1,69 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.json; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +import java.util.Map; + +/** + * A {@code public class} responsible for merging JSON data from a source {@link JsonObject} into a target {@link JsonObject}. + *

    + * This class provides functionality to combine JSON data where overlapping keys result in merging arrays, + * and non-overlapping keys are simply added to the target. + *

    + * + *

    + * Key Methods: + *

      + *
    • {@link #mergeJsonObjects(JsonObject, JsonObject)} - Merges the data from the source JSON object into the target JSON object.
    • + *
    + * + * @author MeAlam + * @since 1.0.0 + */ +public class JSONMerger { + + /** + * A {@code public void} method that merges data from a source {@link JsonObject} into a target {@link JsonObject}. + *

    + * If the target JSON object already contains a key present in the source JSON object, the values are merged if they are arrays. + * Otherwise, the source value is added to the target JSON object. + *

    + * + * @param pTarget {@link JsonObject} - The target {@link JsonObject} to merge data into. This object will be modified by adding or updating its values. + * @param pSource {@link JsonObject} - The source {@link JsonObject} to merge data from. This object is not modified by the operation. + */ + public void mergeJsonObjects(JsonObject pTarget, JsonObject pSource) { + + for (Map.Entry entry : pSource.entrySet()) { + String key = entry.getKey(); + JsonElement sourceElement = entry.getValue(); + + if (pTarget.has(key)) { + JsonElement targetElement = pTarget.get(key); + + if (targetElement.isJsonArray() && sourceElement.isJsonArray()) { + JsonArray targetArray = targetElement.getAsJsonArray(); + JsonArray sourceArray = sourceElement.getAsJsonArray(); + + for (JsonElement element : sourceArray) { + targetArray.add(element); + } + + BaseLogger.log(BaseLogLevel.ERROR, "Merged array for key: " + key, true); + } else { + pTarget.add(key, sourceElement); + BaseLogger.log(BaseLogLevel.WARNING, "Overwriting value for key: " + key, true); + } + } else { + pTarget.add(key, sourceElement); + BaseLogger.log(BaseLogLevel.SUCCESS, "Added new key: " + key, true); + } + } + } +} diff --git a/neoforge/src/main/java/software/bluelib/platform/NeoForgePlatformHelper.java b/neoforge/src/main/java/software/bluelib/platform/NeoForgePlatformHelper.java new file mode 100644 index 00000000..5cb1f2c2 --- /dev/null +++ b/neoforge/src/main/java/software/bluelib/platform/NeoForgePlatformHelper.java @@ -0,0 +1,66 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.platform; + +import net.neoforged.fml.ModList; +import net.neoforged.fml.loading.FMLLoader; +import software.bluelib.interfaces.platform.IPlatformHelper; + +/** + * A {@link NeoForgePlatformHelper} class that provides platform-specific implementation for NeoForge. + *

    + * This class implements {@link IPlatformHelper} to provide NeoForge-specific functionality such as + * retrieving the platform name, checking if a mod is loaded, and determining if the game is running + * in a development environment. + *

    + * + * Key Methods: + *
      + *
    • {@link #getPlatformName()} - Returns the platform name for NeoForge.
    • + *
    • {@link #isModLoaded(String)} - Checks if a mod is loaded using NeoForge's {@link ModList}.
    • + *
    • {@link #isDevelopmentEnvironment()} - Checks if NeoForge is running in a development environment.
    • + *
    + * + * @author MeAlam + * @since 1.0.0 + */ +public class NeoForgePlatformHelper implements IPlatformHelper { + + /** + * A {@code public} {@link String} method that returns the name of the current platform, which is "NeoForge" for this implementation. + * + * @return {@link String} - The platform name, "NeoForge". + * @author MeAlam + * @since 1.0.0 + */ + @Override + public String getPlatformName() { + return "NeoForge"; + } + + /** + * A {@code public} {@link Boolean} method that checks if a mod with the given ID is loaded using NeoForge's {@link ModList}. + * + * @param pModId {@link String} - The mod ID to check if it's loaded. + * @return {@code true} if the mod is loaded, {@code false} otherwise. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public boolean isModLoaded(String pModId) { + return ModList.get().isLoaded(pModId); + } + + /** + * A {@code public} {@link Boolean} method that checks if the game is currently running in a development environment + * using NeoForge's {@link FMLLoader}. + * + * @return {@code true} if running in a development environment, {@code false} if it isn't. + * @author MeAlam + * @since 1.0.0 + */ + @Override + public boolean isDevelopmentEnvironment() { + return !FMLLoader.isProduction(); + } +} diff --git a/neoforge/src/main/java/software/bluelib/utils/minecraft/ChunkUtils.java b/neoforge/src/main/java/software/bluelib/utils/minecraft/ChunkUtils.java new file mode 100644 index 00000000..52704d81 --- /dev/null +++ b/neoforge/src/main/java/software/bluelib/utils/minecraft/ChunkUtils.java @@ -0,0 +1,233 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.utils.minecraft; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.chunk.LevelChunk; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +import java.util.Arrays; +import java.util.Collection; +import java.util.stream.Collectors; + +/** + * A {@code class} providing methods to interact with Minecraft chunks, + * specifically for retrieving biome and tile entity information. + *

    + * Key Methods: + *

      + *
    • {@link #getBiomeOfChunk(Level, ChunkPos)} - Retrieves the {@link Biome} of the specified chunk.
    • + *
    • {@link #getBiomeRegistryNameOfChunk(Level, ChunkPos)} - Retrieves the biome registry name of the specified chunk.
    • + *
    • {@link #getBiomeSimpleNameOfChunk(Level, ChunkPos)} - Retrieves the simple name of the biome in the specified chunk.
    • + *
    • {@link #getChunkTileEntities(Level, ChunkPos)} - Retrieves the tile entities within the specified chunk.
    • + *
    • {@link #getChunkTileEntitiesRegistryNames(Level, ChunkPos)} - Retrieves the registry names of tile entities in the specified chunk.
    • + *
    • {@link #getChunkTileEntitiesSimpleNames(Level, ChunkPos)} - Retrieves the simple names of tile entities in the specified chunk.
    • + *
    • {@link #getChunkBlockCount(Level, ChunkPos)} - Counts the number of non-air blocks in the specified chunk.
    • + *
    + * + * @author MeAlam + * @since 1.0.0 + */ +public class ChunkUtils { + + /** + * Private constructor to prevent instantiation. + *

    + * This constructor is intentionally empty to prevent creating instances of this utility class. + *

    + * + * @author MeAlam + * @since 1.0.0 + */ + private ChunkUtils() { + } + + /** + * A {@link Biome} that retrieves the {@link Biome} of the specified chunk. + *

    + * Logs a success message if the biome is retrieved successfully, + * and an error message if an exception occurs. + *

    + * + * @param pLevel {@link Level} - The game world level. + * @param pChunkPos {@link ChunkPos} - The position of the chunk. + * @return The {@link Biome} associated with the specified chunk. + * @throws RuntimeException if there is an error retrieving the biome. + */ + public static Biome getBiomeOfChunk(Level pLevel, ChunkPos pChunkPos) { + try { + return pLevel.getBiome(pChunkPos.getWorldPosition()).value(); + } catch (Exception pException) { + BaseLogger.log(BaseLogLevel.ERROR, "Error retrieving biome for chunk at position " + pChunkPos, pException, true); + throw pException; + } + } + + + /** + * A {@link String} that retrieves the biome registry name of the specified chunk. + *

    + * Example: "minecraft:plains", "minecraft:desert" + *

    + * + * @param pLevel {@link Level} - The game world level. + * @param pChunkPos {@link ChunkPos} - The position of the chunk. + * @return The registry name of the chunk's biome as a {@link String}. + * @throws RuntimeException if there is an error retrieving the biome registry name. + */ + public static String getBiomeRegistryNameOfChunk(Level pLevel, ChunkPos pChunkPos) { + ResourceLocation biomeKey = pLevel.registryAccess() + .registryOrThrow(Registries.BIOME) + .getKey(pLevel.getBiome(pChunkPos.getWorldPosition()).value()); + + if (biomeKey == null) { + NullPointerException exception = new NullPointerException("Biome at chunk position " + pChunkPos + " is null"); + BaseLogger.log(BaseLogLevel.ERROR, "Error retrieving biome registry name of chunk at " + pChunkPos, exception, true); + return exception.getMessage(); + } + return biomeKey.toString(); + } + + + /** + * A {@link String} that retrieves the simple name of the biome in the specified chunk. + *

    + * Example: "plains", "desert" + *

    + * + * @param pLevel {@link Level} - The game world level. + * @param pChunkPos {@link ChunkPos} - The position of the chunk. + * @return The simple name of the chunk's biome. + */ + public static String getBiomeSimpleNameOfChunk(Level pLevel, ChunkPos pChunkPos) { + String registryName = getBiomeRegistryNameOfChunk(pLevel, pChunkPos); + return registryName.contains(":") ? registryName.split(":")[1] : registryName; + } + + /** + * A {@link Collection} that retrieves the tile entities within the specified chunk. + *

    + * Logs a success message with the number of tile entities retrieved, + * and an error message if an exception occurs. + *

    + * + * @param pLevel {@link Level} - The game world level. + * @param pChunkPos {@link ChunkPos} - The position of the chunk. + * @return A collection of tile entities present in the specified chunk. + * @throws RuntimeException if there is an error retrieving tile entities. + */ + public static Collection getChunkTileEntities(Level pLevel, ChunkPos pChunkPos) { + try { + LevelChunk chunk = pLevel.getChunk(pChunkPos.x, pChunkPos.z); + return chunk.getBlockEntities().values(); + } catch (Exception pException) { + BaseLogger.log(BaseLogLevel.ERROR, "Error retrieving tile entities for chunk at position " + pChunkPos, pException, true); + throw pException; + } + } + + + /** + * A {@link String} that retrieves the registry names of tile entities in the specified chunk. + *

    + * Example: "minecraft:chest, minecraft:furnace" + *

    + * + * @param pLevel {@link Level} - The game world level. + * @param pChunkPos {@link ChunkPos} - The position of the chunk. + * @return A comma-separated string of tile entity registry names in the chunk. + * @throws RuntimeException if there is an error retrieving tile entity registry names. + */ + public static String getChunkTileEntitiesRegistryNames(Level pLevel, ChunkPos pChunkPos) { + try { + Collection blockEntities = getChunkTileEntities(pLevel, pChunkPos); + + return blockEntities.stream() + .map(blockEntity -> { + ResourceLocation key = pLevel.registryAccess() + .registryOrThrow(Registries.BLOCK_ENTITY_TYPE) + .getKey(blockEntity.getType()); + + return key != null ? key.toString() : "unknown"; + }) + .collect(Collectors.joining(", ")); + } catch (Exception pException) { + BaseLogger.log(BaseLogLevel.ERROR, "Error retrieving tile entity registry names for chunk at position " + pChunkPos, pException, true); + throw pException; + } + } + + /** + * A {@link String} that retrieves the simple names of tile entities in the specified chunk. + *

    + * Example: "chest, furnace" + *

    + * + * @param pLevel {@link Level} - The game world level. + * @param pChunkPos {@link ChunkPos} - The position of the chunk. + * @return A comma-separated string of tile entity simple names in the chunk. + */ + public static String getChunkTileEntitiesSimpleNames(Level pLevel, ChunkPos pChunkPos) { + String registryNames = getChunkTileEntitiesRegistryNames(pLevel, pChunkPos); + + return Arrays.stream(registryNames.split(", ")) + .map(fullName -> fullName.contains(":") ? fullName.split(":")[1] : fullName) + .collect(Collectors.joining(", ")); + } + + /** + * A {@link Integer} that counts the number of non-air blocks in the specified chunk. + *

    + * Logs a success message with the block count, + * and an error message if an exception occurs. + *

    + * + * @param pLevel {@link Level} - The game world level. + * @param pChunkPos {@link ChunkPos} - The position of the chunk. + * @return The number of non-air blocks in the specified chunk. + * @throws RuntimeException if there is an error counting blocks. + */ + public static int getChunkBlockCount(Level pLevel, ChunkPos pChunkPos) { + try { + LevelChunk chunk = pLevel.getChunk(pChunkPos.x, pChunkPos.z); + int blockCount = 0; + + for (int x = 0; x < 16; x++) { + for (int y = pLevel.getMinBuildHeight(); y < pLevel.getHeight(); y++) { + for (int z = 0; z < 16; z++) { + BlockPos worldPos = new BlockPos(pChunkPos.getMinBlockX() + x, y, pChunkPos.getMinBlockZ() + z); + if (!chunk.getBlockState(worldPos).isAir()) { + blockCount++; + } + } + } + } + return blockCount; + } catch (Exception pException) { + BaseLogger.log(BaseLogLevel.ERROR, "Error counting blocks for chunk at position " + pChunkPos, pException, true); + throw pException; + } + } + + /** FIXME: This method is not working as expected. It is not returning correctly. + public static boolean isChunkLoaded(final LevelAccessor pWorld, final int pX, final int pZ) { + try { + boolean isLoaded = pWorld.getChunk(pX, pZ, ChunkStatus.FULL, false) != null; + BaseLogger.bluelibLogSuccess("Chunk at (" + pX + ", " + pZ + ") is loaded: " + isLoaded); + return isLoaded; + } catch (Exception e) { + BaseLogger.logError("Error checking if chunk at (" + pX + ", " + pZ + ") is loaded", e); + return false; + } + } + */ + + +} diff --git a/neoforge/src/main/java/software/bluelib/utils/variant/ParameterUtils.java b/neoforge/src/main/java/software/bluelib/utils/variant/ParameterUtils.java new file mode 100644 index 00000000..da316636 --- /dev/null +++ b/neoforge/src/main/java/software/bluelib/utils/variant/ParameterUtils.java @@ -0,0 +1,184 @@ +// Copyright (c) BlueLib. Licensed under the MIT License. + +package software.bluelib.utils.variant; + +import software.bluelib.entity.variant.VariantLoader; +import software.bluelib.entity.variant.VariantParameter; +import software.bluelib.utils.logging.BaseLogLevel; +import software.bluelib.utils.logging.BaseLogger; + +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; + +/** + * A utility class for managing custom parameters associated with entity variants. + *

    + * Provides methods to retrieve custom parameters for variants and allows for + * building and connecting parameters to specific variants via the {@link ParameterBuilder} class. + *

    + *

    + * Key Methods: + *

      + *
    • {@link #getParameter(String, String)} - Retrieves the value of a custom parameter for a specific variant.
    • + *
    + *

    + * Nested Classes: + *

      + *
    • {@link ParameterBuilder} - Builder class for creating and associating custom parameters with variants.
    • + *
    + * + * @author MeAlam + * @version 1.0.0 + * @see VariantParameter + * @since 1.0.0 + */ +public class ParameterUtils { + + /** + * Private constructor to prevent instantiation. + *

    + * This constructor is intentionally empty to prevent creating instances of this utility class. + *

    + * + * @author MeAlam + * @since 1.0.0 + */ + private ParameterUtils() { + } + + /** + * Holds custom parameters for each variant. + *

    + * The outer map's key is the variant name, and the inner map contains key-value pairs + * representing custom parameters for that variant. + *

    + * + * @since 1.0.0 + */ + private static final Map> variantParametersMap = new HashMap<>(); + + /** + * A {@link String} that retrieves the value of a custom parameter for a specific variant. + *

    + * If the parameter is not found, {@code null} is returned. + *

    + * + * @param pVariantName {@link String} The name of the variant. + * @param pParameterKey {@link String} The key of the parameter to retrieve. + * @return {@link String} The value of the custom parameter for the specified variant or {@code null} if not found. + * @author MeAlam + * @since 1.0.0 + */ + public static String getParameter(String pVariantName, String pParameterKey) { + return variantParametersMap.getOrDefault(pVariantName, new HashMap<>()).getOrDefault(pParameterKey, "null"); + } + + /** + * A {@code class} for creating and associating custom parameters with a specific variant. + *

    + * Allows chaining methods to build and connect parameters to a variant. + *

    + *

    + * Key Methods: + *

      + *
    • {@link #forVariant(String, String)} - Creates a new instance of {@link ParameterBuilder} for a specific entity and variant.
    • + *
    • {@link #withParameter(String)} - Adds a parameter with a default value of {@code null} .
    • + *
    • {@link #connect()} - Connects the parameters to the variant and updates {@link VariantParameter} with the parameters.
    • + *
    + * + * @author MeAlam + * @since 1.0.0 + */ + public static class ParameterBuilder { + + /** + * The name of the variant being associated with custom parameters. + * + * @since 1.0.0 + */ + private final String variantName; + + /** + * The name of the entity being associated with custom parameters. + * + * @since 1.0.0 + */ + private final String entityName; + + /** + * Stores custom parameters being built for the variant. + * + * @since 1.0.0 + */ + private final Map parameters = new HashMap<>(); + + /** + * Constructor to initialize the builder for a specific entity and variant. + * + * @param pEntityName {@link String} The name of the entity. + * @param pVariantName {@link String} The name of the variant. + * @author MeAlam + * @since 1.0.0 + */ + private ParameterBuilder(String pEntityName, String pVariantName) { + this.variantName = pVariantName; + this.entityName = pEntityName; + } + + /** + * A {@link ParameterBuilder} that creates a new instance of {@link ParameterBuilder} for the specified entity and variant. + * + * @param pEntityName {@link String} The name of the entity. + * @param pVariantName {@link String} The name of the variant. + * @return {@link ParameterBuilder} A new instance for chaining. + * @author MeAlam + * @since 1.0.0 + */ + public static ParameterBuilder forVariant(String pEntityName, String pVariantName) { + return new ParameterBuilder(pEntityName, pVariantName); + } + + /** + * A {@link ParameterBuilder} that adds a custom parameter to the builder with a default value of "null". + *

    + * The {@code null} value is used if the parameter is not specified in the data source. + *

    + * + * @param pParameter {@link String} The parameter key. + * @return {@link ParameterBuilder} The builder instance for chaining. + * @author MeAlam + * @since 1.0.0 + */ + public ParameterBuilder withParameter(String pParameter) { + parameters.put(pParameter, "null"); + return this; + } + + /** + * A {@link ParameterBuilder} that connects the custom parameters to the specified variant and updates the {@link VariantParameter}. + *

    + * Throws a {@link NoSuchElementException} if the variant or entity is not found. + *

    + * + * @return {@link ParameterBuilder} The builder instance for chaining. + * @throws NoSuchElementException if the variant or entity is not found in the database. + * @author MeAlam + * @since 1.0.0 + */ + public ParameterBuilder connect() { + VariantParameter variant = VariantLoader.getVariantByName(entityName, variantName); + if (variant != null) { + Map updatedParameters = new HashMap<>(); + for (String key : parameters.keySet()) { + updatedParameters.put(key, variant.getParameter(key)); + } + variantParametersMap.put(variantName, updatedParameters); + } else { + Throwable throwable = new Throwable("Variant or entity not found in the database"); + BaseLogger.log(BaseLogLevel.ERROR, "Variant '" + variantName + "' not found for entity '" + entityName + "'", throwable, true); + } + return this; + } + } +} diff --git a/neoforge/src/main/resources/META-INF/mods.toml b/neoforge/src/main/resources/META-INF/mods.toml new file mode 100644 index 00000000..cce7d453 --- /dev/null +++ b/neoforge/src/main/resources/META-INF/mods.toml @@ -0,0 +1,29 @@ +modLoader="javafml" +loaderVersion="${neoforge_loader_version_range}" +license="${license}" +issueTrackerURL="https://github.com/MeAlam1/BlueLib/issues" + +[[mods]] +modId="${mod_id}" +version="${version}" +displayName="${mod_name}" +# A URL to query for updates for this mod. See the JSON update specification https://docs.neoforged.net/docs/misc/updatechecker/ +#updateJSONURL="https://change.me.example.invalid/updates.json" #optional +displayURL="https://mealam1.github.io/BlueLib/" +logoFile="bluelib.png" +credits="Anyone who contributed to the Source Code of BlueLib!" +authors="${mod_author}" +description='''${description}''' + +[[dependencies.bluelib]] +modId = "neoforge" +mandatory = true +versionRange = "${neoforge_loader_version_range}" +ordering = "NONE" +side = "BOTH" +[[dependencies.bluelib]] +modId = "minecraft" +mandatory = true +versionRange = "${minecraft_version_range}" +ordering = "NONE" +side = "BOTH" \ No newline at end of file diff --git a/neoforge/src/main/resources/META-INF/services/software.bluelib.interfaces.platform.IPlatformHelper b/neoforge/src/main/resources/META-INF/services/software.bluelib.interfaces.platform.IPlatformHelper new file mode 100644 index 00000000..f06837d3 --- /dev/null +++ b/neoforge/src/main/resources/META-INF/services/software.bluelib.interfaces.platform.IPlatformHelper @@ -0,0 +1 @@ +software.bluelib.platform.NeoForgePlatformHelper \ No newline at end of file diff --git a/neoforge/src/main/resources/assets/bluelib/animations/dragon.animation.json b/neoforge/src/main/resources/assets/bluelib/animations/dragon.animation.json new file mode 100644 index 00000000..0b02f765 --- /dev/null +++ b/neoforge/src/main/resources/assets/bluelib/animations/dragon.animation.json @@ -0,0 +1,63 @@ +{ + "format_version": "1.8.0", + "animations": { + "test": { + "loop": true, + "bones": { + "Body": { + "rotation": { + "vector": [-20.44582, 11.73507, -4.3361], + "easing": "linear" + }, + "position": { + "vector": [0, -1, 0], + "easing": "linear" + } + }, + "leftleg": { + "rotation": { + "vector": [0, 22.5, 0], + "easing": "linear" + } + }, + "rightleg": { + "rotation": { + "vector": [0, -22.5, 0], + "easing": "linear" + } + }, + "rightwing1": { + "rotation": { + "vector": [0, 0, -20], + "easing": "linear" + } + }, + "leftwing": { + "rotation": { + "vector": [0, 0, -10], + "easing": "linear" + } + }, + "leftwing1": { + "rotation": { + "vector": [0, 0, 30], + "easing": "linear" + } + }, + "neck": { + "rotation": { + "vector": [-40, 0, 0], + "easing": "linear" + } + }, + "Head": { + "rotation": { + "vector": [72.5, 0, 0], + "easing": "linear" + } + } + } + } + }, + "geckolib_format_version": 2 +} \ No newline at end of file diff --git a/neoforge/src/main/resources/assets/bluelib/animations/rex.animation.json b/neoforge/src/main/resources/assets/bluelib/animations/rex.animation.json new file mode 100644 index 00000000..5d8891b4 --- /dev/null +++ b/neoforge/src/main/resources/assets/bluelib/animations/rex.animation.json @@ -0,0 +1,51 @@ +{ + "format_version": "1.8.0", + "animations": { + "animation2": { + "loop": true, + "bones": { + "body": { + "rotation": { + "vector": [0, -15, 0] + } + }, + "tail2": { + "rotation": { + "vector": [0, 20, 0] + } + }, + "tail3": { + "rotation": { + "vector": [0, 20, 0] + } + }, + "tail4": { + "rotation": { + "vector": [0, 20, 0] + } + }, + "tail5": { + "rotation": { + "vector": [0, 20, 0] + } + }, + "tail6": { + "rotation": { + "vector": [0, 20, 0] + } + }, + "neck": { + "rotation": { + "vector": [0, -2.5, 0] + } + }, + "head": { + "rotation": { + "vector": [0, -20, 0] + } + } + } + } + }, + "geckolib_format_version": 2 +} \ No newline at end of file diff --git a/neoforge/src/main/resources/assets/bluelib/geo/dragon.geo.json b/neoforge/src/main/resources/assets/bluelib/geo/dragon.geo.json new file mode 100644 index 00000000..8f038886 --- /dev/null +++ b/neoforge/src/main/resources/assets/bluelib/geo/dragon.geo.json @@ -0,0 +1,93 @@ +{ + "format_version": "1.12.0", + "minecraft:geometry": [ + { + "description": { + "identifier": "geometry.unknown", + "texture_width": 64, + "texture_height": 64, + "visible_bounds_width": 4, + "visible_bounds_height": 2.5, + "visible_bounds_offset": [0, 0.75, 0] + }, + "bones": [ + { + "name": "Body", + "pivot": [-0.16667, 5, -1], + "cubes": [ + {"origin": [-2, 4, 1], "size": [4, 2, 4], "uv": [17, 10]}, + {"origin": [-3, 4, -4], "size": [6, 2, 5], "uv": [0, 14]} + ] + }, + { + "name": "leftleg", + "parent": "Body", + "pivot": [1.5, 5, 5], + "cubes": [ + {"origin": [1.5, 3, 4.5], "size": [0, 4, 3], "pivot": [1.5, 5, 6], "rotation": [90, 0, 90], "uv": [0, 0]} + ] + }, + { + "name": "rightleg", + "parent": "Body", + "pivot": [-1.5, 5, 5], + "cubes": [ + {"origin": [-1.5, 3, 4.5], "size": [0, 4, 3], "pivot": [-1.5, 5, 6], "rotation": [90, 0, -90], "uv": [0, 0], "mirror": true} + ] + }, + { + "name": "rightwing", + "parent": "Body", + "pivot": [-2.5, 5, -3.5], + "cubes": [ + {"origin": [-10, 5, -5], "size": [7, 0, 7], "uv": [-7, 28], "mirror": true} + ] + }, + { + "name": "rightwing1", + "parent": "rightwing", + "pivot": [-9.5, 5, -1.5], + "cubes": [ + {"origin": [-17, 5, -5], "size": [7, 0, 7], "uv": [43, 0], "mirror": true} + ] + }, + { + "name": "leftwing", + "parent": "Body", + "pivot": [2.5, 5, -3.5], + "cubes": [ + {"origin": [3, 5, -5], "size": [7, 0, 7], "uv": [-7, 28]} + ] + }, + { + "name": "leftwing1", + "parent": "leftwing", + "pivot": [9.5, 5, -1.5], + "cubes": [ + {"origin": [10, 5, -5], "size": [7, 0, 7], "uv": [43, 0]} + ] + }, + { + "name": "neck", + "parent": "Body", + "pivot": [0, 5, -4], + "cubes": [ + {"origin": [-1, 4, -8], "size": [2, 2, 4], "uv": [10, 21]} + ] + }, + { + "name": "Head", + "parent": "neck", + "pivot": [0, 4.8, -7.5], + "cubes": [ + {"origin": [-1.5, 4, -12], "size": [3, 3, 4], "uv": [18, 17]}, + {"origin": [0, 6, -15], "size": [0, 3, 7], "uv": [0, 14]}, + {"origin": [-1, 4, -16], "size": [2, 2, 4], "uv": [21, 0]}, + {"origin": [-1, 3, -16], "size": [2, 1, 1], "uv": [29, 0]}, + {"origin": [0, 7, -8], "size": [0, 1, 3], "uv": [0, 23]} + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/neoforge/src/main/resources/assets/bluelib/geo/rex.geo.json b/neoforge/src/main/resources/assets/bluelib/geo/rex.geo.json new file mode 100644 index 00000000..3d18bac7 --- /dev/null +++ b/neoforge/src/main/resources/assets/bluelib/geo/rex.geo.json @@ -0,0 +1,390 @@ +{ + "format_version": "1.12.0", + "minecraft:geometry": [ + { + "description": { + "identifier": "geometry.unknown", + "texture_width": 512, + "texture_height": 512, + "visible_bounds_width": 29, + "visible_bounds_height": 7.5, + "visible_bounds_offset": [0, 3.25, 0] + }, + "bones": [ + { + "name": "Tyrannosaurus", + "pivot": [0, 0, 0] + }, + { + "name": "body", + "parent": "Tyrannosaurus", + "pivot": [0, 72, 0], + "cubes": [ + {"origin": [-19, 47, -8], "size": [38, 45, 34], "uv": [124, 51]}, + {"origin": [-15.475, 51.23327, -14.01496], "size": [31, 45, 35], "pivot": [0.025, 74.40827, 4.38504], "rotation": [-12.75, 0, 0], "uv": [130, 50]}, + {"origin": [-16, 40, -22.525], "size": [32, 16, 40], "inflate": -0.05, "pivot": [0, 72, 0.275], "rotation": [17.5, 0, 0], "uv": [0, 160]}, + {"origin": [-16, 54, -54], "size": [32, 37, 46], "uv": [1, 1]}, + {"origin": [-16, 71.46448, -54.53145], "size": [32, 30, 48], "inflate": -0.25, "pivot": [0, 89.46448, 3.46855], "rotation": [8.75, 0, 0], "uv": [-1, -1]}, + {"origin": [-16, 28.37894, -42.36923], "size": [32, 21, 49], "inflate": -0.025, "pivot": [0, 73.37894, 12.63077], "rotation": [-24, 0, 0], "uv": [0, 85]} + ] + }, + { + "name": "arm2", + "parent": "body", + "pivot": [13.56731, 58.46159, -45.9607], + "rotation": [29.07337, 19.05681, -20.20848], + "cubes": [ + {"origin": [12.56731, 47.46159, -47.9607], "size": [3, 11, 5], "uv": [111, 0]} + ] + }, + { + "name": "bone2", + "parent": "arm2", + "pivot": [15.06274, 49.29312, -45.32771], + "rotation": [-27.154, -2.96386, 4.54476], + "cubes": [ + {"origin": [13.13695, 39.306, -44.80271], "size": [2, 6, 2], "inflate": -0.075, "pivot": [14.11195, 46.806, -45.02771], "rotation": [0, 0, 15], "uv": [0, 0]}, + {"origin": [14.86195, 37.306, -44.75271], "size": [0, 3, 2], "pivot": [14.11195, 46.806, -45.02771], "rotation": [0, 0, 15], "uv": [36, 26]}, + {"origin": [12.13695, 44.306, -47.52771], "size": [3, 5, 5], "inflate": -0.125, "pivot": [14.11195, 46.806, -45.02771], "rotation": [0, 0, 15], "uv": [165, 257]} + ] + }, + { + "name": "bone3", + "parent": "bone2", + "pivot": [13.25685, 45.11756, -46.37771], + "rotation": [-14.50257, -2.95519, -2.10666], + "cubes": [ + {"origin": [13.13695, 41.306, -47.37771], "size": [2, 4, 2], "inflate": -0.075, "pivot": [14.11195, 46.806, -45.02771], "rotation": [0, 0, 15], "uv": [0, 28]}, + {"origin": [14.86195, 39.306, -47.37771], "size": [0, 3, 2], "pivot": [14.11195, 46.806, -45.02771], "rotation": [0, 0, 15], "uv": [36, 29]} + ] + }, + { + "name": "arm3", + "parent": "body", + "pivot": [-13.56731, 58.46159, -45.9607], + "rotation": [29.07337, -19.05681, 20.20848], + "cubes": [ + {"origin": [-15.56731, 47.46159, -47.9607], "size": [3, 11, 5], "uv": [111, 0], "mirror": true} + ] + }, + { + "name": "bone4", + "parent": "arm3", + "pivot": [-15.06274, 49.29312, -45.32771], + "rotation": [-27.154, 2.96386, -4.54476], + "cubes": [ + {"origin": [-15.13695, 39.306, -44.80271], "size": [2, 6, 2], "inflate": -0.075, "pivot": [-14.11195, 46.806, -45.02771], "rotation": [0, 0, -15], "uv": [0, 0], "mirror": true}, + {"origin": [-14.86195, 37.306, -44.75271], "size": [0, 3, 2], "pivot": [-14.11195, 46.806, -45.02771], "rotation": [0, 0, -15], "uv": [36, 26], "mirror": true}, + {"origin": [-15.13695, 44.306, -47.52771], "size": [3, 5, 5], "inflate": -0.125, "pivot": [-14.11195, 46.806, -45.02771], "rotation": [0, 0, -15], "uv": [165, 257], "mirror": true} + ] + }, + { + "name": "bone5", + "parent": "bone4", + "pivot": [-13.25685, 45.11756, -46.37771], + "rotation": [-14.50257, 2.95519, 2.10666], + "cubes": [ + {"origin": [-15.13695, 41.306, -47.37771], "size": [2, 4, 2], "inflate": -0.075, "pivot": [-14.11195, 46.806, -45.02771], "rotation": [0, 0, -15], "uv": [0, 28], "mirror": true}, + {"origin": [-14.86195, 39.306, -47.37771], "size": [0, 3, 2], "pivot": [-14.11195, 46.806, -45.02771], "rotation": [0, 0, -15], "uv": [36, 29], "mirror": true} + ] + }, + { + "name": "tail1", + "parent": "body", + "pivot": [0, 82.97049, 21.3522], + "cubes": [ + {"origin": [-15, 55.74549, 21.0272], "size": [30, 30, 39], "uv": [123, 128]}, + {"origin": [-15, 71.63781, 17.37365], "size": [30, 28, 35], "inflate": -0.2, "pivot": [0, 60.21281, -16.55135], "rotation": [-10.75, 0, 0], "uv": [127, 132]}, + {"origin": [-15, 39.02049, 26.7772], "size": [30, 13, 37], "inflate": -0.025, "pivot": [0, 30.97049, 1.3522], "rotation": [16, 0, 0], "uv": [159, 1]} + ] + }, + { + "name": "tail2", + "parent": "tail1", + "pivot": [0, 82.97049, 56.3522], + "cubes": [ + {"origin": [-10, 62.97049, 56.3522], "size": [20, 21, 38], "uv": [106, 199]}, + {"origin": [-9, 41.02049, 63.3522], "size": [18, 14, 35], "inflate": -0.025, "pivot": [0, 30.97049, 1.3522], "rotation": [13, 0, 0], "uv": [173, 3]} + ] + }, + { + "name": "tail3", + "parent": "tail2", + "pivot": [0, 82.97049, 93.3522], + "cubes": [ + {"origin": [-7, 66.97049, 93.3522], "size": [14, 14, 37], "uv": [0, 216]}, + {"origin": [-7, 54.52049, 94.3522], "size": [14, 7, 38], "inflate": -0.025, "pivot": [0, 30.97049, 1.3522], "rotation": [5.5, 0, 0], "uv": [174, 0]} + ] + }, + { + "name": "tail4", + "parent": "tail3", + "pivot": [0, 82.97049, 129.3522], + "cubes": [ + {"origin": [-5, 69.97049, 129.3522], "size": [10, 9, 37], "uv": [255, 14]}, + {"origin": [-5, 64.82049, 132.6022], "size": [10, 4, 37], "inflate": -0.025, "pivot": [0, 30.97049, 109.3522], "rotation": [4.75, 0, 0], "uv": [255, 14]} + ] + }, + { + "name": "tail5", + "parent": "tail4", + "pivot": [0, 82.97049, 158.3522], + "cubes": [ + {"origin": [-3, 70.97049, 158.3522], "size": [6, 6, 37], "uv": [149, 257]}, + {"origin": [-3, 69.22961, 165.59014], "size": [6, 3, 30], "inflate": -0.1, "pivot": [0, 70.15461, 147.99014], "rotation": [0.5, 0, 0], "uv": [266, 21]} + ] + }, + { + "name": "tail6", + "parent": "tail5", + "pivot": [0, 82.97049, 182.3522], + "cubes": [ + {"origin": [-2, 71.97049, 182.3522], "size": [4, 4, 37], "uv": [0, 285]}, + {"origin": [-2, 66.99549, 197.7022], "size": [4, 4, 25], "inflate": -0.175, "pivot": [0, 30.97049, 162.3522], "rotation": [4.75, 0, 0], "uv": [12, 297]} + ] + }, + { + "name": "neck", + "parent": "body", + "pivot": [0, 74.22605, -48.50511], + "cubes": [ + {"origin": [-15, 55, -67], "size": [30, 31, 17], "uv": [251, 113]}, + {"origin": [-14.25, 80.39311, -64.98257], "size": [30, 11, 17], "inflate": -1, "pivot": [0.75, 82.39311, -53.98257], "rotation": [24.25, 0, 0], "uv": [251, 113]}, + {"origin": [-10, 66.05, -70.125], "size": [20, 34, 25], "inflate": -0.225, "pivot": [0, 87.575, -69.125], "rotation": [-39.25, 0, 0], "uv": [221, 200]}, + {"origin": [-10, 59.575, -78.125], "size": [20, 13, 25], "inflate": -1.325, "pivot": [0, 87.575, -69.125], "rotation": [-14.25, 0, 0], "uv": [235, 259]}, + {"origin": [-9, 71.2, -85.125], "size": [18, 19, 24], "inflate": -0.325, "pivot": [0, 85.2, -69.125], "rotation": [7.5, 0, 0], "uv": [268, 61]}, + {"origin": [-8, 83.89159, -88.09601], "size": [16, 12, 13], "inflate": -0.275, "pivot": [0, 89.89159, -79.09601], "rotation": [42.5, 0, 0], "uv": [198, 259]}, + {"origin": [-8, 86.39159, -80.39601], "size": [16, 11, 19], "inflate": -0.25, "pivot": [0, 89.89159, -68.09601], "rotation": [2.5, 0, 0], "uv": [111, 0]} + ] + }, + { + "name": "head", + "parent": "neck", + "pivot": [0, 83.05369, -83.57238], + "cubes": [ + {"origin": [-8.725, 71, -101], "size": [17, 19, 19], "uv": [286, 165]}, + {"origin": [-8.775, 84, -101], "size": [17, 2, 7], "inflate": 0.225, "uv": [104, 155]}, + {"origin": [-7, 93.5819, -99.5906], "size": [14, 5, 10], "inflate": -0.05, "pivot": [0, 90.5819, -77.5906], "rotation": [21.5, 0, 0], "uv": [0, 111]}, + {"origin": [-7, 81.46532, -93.49693], "size": [14, 5, 9], "inflate": -0.075, "pivot": [0, 78.46532, -74.49693], "rotation": [-25, 0, 0], "uv": [111, 30]}, + {"origin": [-7, 88.64032, -92.02193], "size": [14, 5, 4], "inflate": -0.125, "uv": [0, 191]}, + {"origin": [9, 86, -102], "size": [4, 3, 13], "inflate": -0.025, "pivot": [7, 80.5, -90.5], "rotation": [0, 0, -17.5], "uv": [149, 277]}, + {"origin": [4.59053, 84.6097, -93.1055], "size": [4, 3, 9], "inflate": -0.05, "pivot": [2.59053, 79.1097, -81.6055], "rotation": [0, -27.5, -17.5], "uv": [255, 23]}, + {"origin": [-8.59053, 84.6097, -93.1055], "size": [4, 3, 9], "inflate": -0.05, "pivot": [-2.59053, 79.1097, -81.6055], "rotation": [0, 27.5, 17.5], "uv": [184, 225]}, + {"origin": [12.62776, 90.52364, -107.52067], "size": [4, 3, 11], "inflate": -0.1, "pivot": [10.62776, 85.02364, -91.02067], "rotation": [27.87149, 36.42355, -5.95346], "uv": [0, 239]}, + {"origin": [-16.62776, 90.52364, -107.52067], "size": [4, 3, 11], "inflate": -0.1, "pivot": [-10.62776, 85.02364, -91.02067], "rotation": [27.87149, -36.42355, 5.95346], "uv": [162, 0]}, + {"origin": [-13, 86, -102], "size": [4, 3, 13], "inflate": -0.025, "pivot": [-7, 80.5, -90.5], "rotation": [0, 0, 17.5], "uv": [109, 216]}, + {"origin": [-7, 78.725, -121], "size": [14, 10, 16], "inflate": -0.05, "pivot": [0, 81.725, -99], "rotation": [27.5, 0, 0], "uv": [300, 243]}, + {"origin": [-7, 78.35, -106], "size": [14, 11, 11], "pivot": [0, 81.35, -99], "rotation": [33.5, 0, 0], "uv": [286, 203]}, + {"origin": [-7, 69.519, -125.21974], "size": [14, 11, 7.85], "inflate": -0.075, "pivot": [0, 72.519, -118.21974], "rotation": [90, 0, 0], "uv": [259, 165]}, + {"origin": [-6, 78.519, -128.21974], "size": [11, 0, 7.85], "pivot": [0, 72.519, -118.21974], "rotation": [90, 0, 0], "uv": [141, 30]}, + {"origin": [5, 61.519, -128.21974], "size": [0, 17, 6.85], "pivot": [-2, 72.519, -118.21974], "rotation": [90, 0, 0], "uv": [234, 45]}, + {"origin": [-5, 61.519, -128.21974], "size": [0, 17, 6.85], "pivot": [2, 72.519, -118.21974], "rotation": [90, 0, 0], "uv": [28, 154]}, + {"origin": [-7, 73.394, -115.21974], "size": [14, 10, 6.85], "inflate": -0.1, "pivot": [0, 74.394, -109.21974], "rotation": [110.5, 0, 0], "uv": [65, 237]}, + {"origin": [-7, 84.31148, -108.92962], "size": [14, 4, 6.85], "inflate": -0.15, "pivot": [0, 79.31148, -102.92962], "rotation": [128, 0, 0], "uv": [272, 0]}, + {"origin": [-7, 74.31148, -110.92962], "size": [14, 9, 8.85], "inflate": -0.175, "pivot": [0, 79.31148, -102.92962], "rotation": [88, 0, 0], "uv": [0, 28]}, + {"origin": [-7, 69.019, -122.66974], "size": [14, 10, 7], "inflate": -0.125, "pivot": [0, 72.019, -117.66974], "rotation": [47.5, 0, 0], "uv": [0, 267]} + ] + }, + { + "name": "jaw", + "parent": "head", + "pivot": [0, 73.374, -85.20224], + "cubes": [ + {"origin": [-9.125, 61.47673, -99.81697], "size": [18, 13, 12], "inflate": 0.025, "uv": [184, 200]}, + {"origin": [-9.15, 64.14383, -89.12484], "size": [18, 12, 7], "inflate": 0.075, "pivot": [-3, 73.14383, -68.12484], "rotation": [7.5, 0, 0], "uv": [312, 0]}, + {"origin": [7.8, 70.81044, -97.91351], "size": [1, 6, 9], "uv": [104, 179]}, + {"origin": [7.85, 63.60089, -93.15375], "size": [1, 6, 7], "pivot": [-2.975, 63.60089, -72.15375], "rotation": [-22.5, 0, 0], "uv": [65, 216]}, + {"origin": [-7.125, 72.47673, -98.81697], "size": [14, 14, 16], "inflate": 0.025, "uv": [219, 297]}, + {"origin": [6.375, 68.47673, -105.81697], "size": [0, 18, 14], "uv": [0, 159]}, + {"origin": [-6.625, 68.47673, -105.81697], "size": [0, 18, 14], "uv": [0, 141]}, + {"origin": [7.875, 65.38327, -112.3007], "size": [1, 8, 15], "inflate": -0.05, "pivot": [0, 73.88327, -104.8007], "rotation": [27.5, 0, 0], "uv": [255, 0]}, + {"origin": [-9.125, 65.38327, -112.3007], "size": [1, 8, 15], "inflate": -0.05, "pivot": [0, 73.88327, -104.8007], "rotation": [27.5, 0, 0], "uv": [0, 216]}, + {"origin": [-9.05, 70.81044, -97.91351], "size": [1, 6, 9], "uv": [104, 164]}, + {"origin": [-9.1, 63.60089, -93.15375], "size": [1, 6, 7], "pivot": [2.975, 63.60089, -72.15375], "rotation": [-22.5, 0, 0], "uv": [17, 216]}, + {"origin": [-9.125, 64.31864, -111.04094], "size": [18, 8, 15], "inflate": -0.025, "pivot": [0, 70.56864, -103.54094], "rotation": [17.5, 0, 0], "uv": [149, 300]}, + {"origin": [-6.125, 66.9078, -126.60427], "size": [12, 5, 15], "pivot": [0, 72.4078, -104.10427], "rotation": [15, 0, 0], "uv": [126, 257]}, + {"origin": [-6.125, 71.5078, -125.60427], "size": [12, 5, 0], "pivot": [0, 72.4078, -104.10427], "rotation": [15, 0, 0], "uv": [62, 155]}, + {"origin": [5.725, 71.5078, -125.60427], "size": [0, 5, 17], "pivot": [0, 72.4078, -104.10427], "rotation": [15, 0, 0], "uv": [28, 138]}, + {"origin": [-5.975, 71.5078, -125.60427], "size": [0, 5, 17], "pivot": [0, 72.4078, -104.10427], "rotation": [15, 0, 0], "uv": [0, 109]}, + {"origin": [-6.125, 60.70962, -124.43479], "size": [12, 5, 15], "inflate": -0.05, "pivot": [0, 63.78462, -115.43479], "rotation": [-3.5, 0, 0], "uv": [234, 61]}, + {"origin": [-7.125, 61.71743, -111.29498], "size": [14, 5, 16], "inflate": -0.025, "pivot": [0, 66.69243, -101.79498], "rotation": [9.25, 0, 0], "uv": [65, 216]} + ] + }, + { + "name": "leg_left", + "parent": "Tyrannosaurus", + "pivot": [20.42923, 70.7324, 8.85434], + "rotation": [-15.09173, -1.85092, -0.08398], + "cubes": [ + {"origin": [12.54366, 41.02086, -4.25109], "size": [12.875, 42.825, 23.725], "uv": [79, 257]} + ] + }, + { + "name": "leg_left2", + "parent": "leg_left", + "pivot": [21.99279, 43.52539, 1.69711], + "cubes": [ + {"origin": [12.844, 24.19389, 3.09297], "size": [12.4, 20.375, 15.95], "pivot": [21.944, 35.38139, 0.96797], "rotation": [34, 0, 0], "uv": [279, 297]} + ] + }, + { + "name": "leg_left3", + "parent": "leg_left2", + "pivot": [19.76532, 33.92692, 13.90262], + "rotation": [55, 0, 0], + "cubes": [ + {"origin": [14.22989, 14.97535, 9.92239], "size": [10, 19, 9], "inflate": -0.1, "uv": [0, 0]} + ] + }, + { + "name": "leg_left4", + "parent": "leg_left3", + "pivot": [19.76532, 14.92692, 13.90262], + "rotation": [-60, 0, 0], + "cubes": [ + {"origin": [14.22989, 2.41759, 7.14887], "size": [10, 17, 9], "inflate": 0.375, "uv": [0, 85]} + ] + }, + { + "name": "Claw5", + "parent": "leg_left4", + "pivot": [18.03922, 11.69239, 7.72125], + "rotation": [3.25353, -39.92899, -4.10228], + "cubes": [ + {"origin": [19.68942, 7.60244, 18.32118], "size": [2, 5, 6], "inflate": -0.2, "pivot": [20.68942, 10.90244, 16.32118], "rotation": [4, 5, 0], "uv": [19, 239]}, + {"origin": [15.96316, 8.07744, 15.28926], "size": [2, 3, 4], "inflate": 0.5, "pivot": [20.4918, 5.40696, 3.40745], "rotation": [2.31336, 4.43385, 27.58957], "uv": [29, 0]} + ] + }, + { + "name": "leg_left5", + "parent": "leg_left4", + "pivot": [19.76532, 2.72669, 9.58045], + "rotation": [32.5, 0, 0], + "cubes": [ + {"origin": [14.22989, -4.78265, 7.2517], "size": [10, 8, 8], "inflate": 0.35, "uv": [42, 267]} + ] + }, + { + "name": "Claw3", + "parent": "leg_left5", + "pivot": [14.03922, -1.93376, 9.83799], + "rotation": [-11.28559, 36.03256, -29.0792], + "cubes": [ + {"origin": [15.68942, -5.02371, -6.76194], "size": [2, 4, 9], "inflate": -0.2, "pivot": [16.68942, -2.72371, 1.23806], "rotation": [-4, -5, 0], "uv": [78, 267]}, + {"origin": [11.96316, -5.54871, -1.73002], "size": [2, 3, 12], "inflate": 0.5, "pivot": [16.4918, -8.21919, 14.15179], "rotation": [-2.31336, -4.43385, 27.58957], "uv": [45, 298]}, + {"origin": [13.86316, -1.04871, -0.45502], "size": [2, 3, 12], "inflate": 0.4, "pivot": [18.3918, -3.71919, 15.42679], "rotation": [14.70847, -5.40113, 27.33492], "uv": [263, 297]} + ] + }, + { + "name": "Claw2", + "parent": "leg_left5", + "pivot": [18.00128, -1.52657, 8.77203], + "rotation": [6.46283, 10.46681, -26.49382], + "cubes": [ + {"origin": [19.65148, -4.61652, -7.8279], "size": [2, 4, 9], "inflate": -0.2, "pivot": [20.65148, -2.31652, 0.1721], "rotation": [-8.25367, -3.37646, 28.00254], "uv": [259, 183]}, + {"origin": [15.92522, -5.14152, -2.79598], "size": [2, 3, 12], "inflate": 0.5, "pivot": [20.45386, -7.812, 13.08583], "rotation": [-2.31336, -4.43385, 27.58957], "uv": [0, 284]}, + {"origin": [17.82522, -0.64152, -1.52098], "size": [2, 3, 12], "inflate": 0.4, "pivot": [22.35386, -3.312, 14.36083], "rotation": [14.70847, -5.40113, 27.33492], "uv": [45, 283]} + ] + }, + { + "name": "Claw4", + "parent": "leg_left5", + "pivot": [25.00339, -1.78444, 9.60342], + "rotation": [-6.62428, -31.83686, 25.40349], + "cubes": [ + {"origin": [21.35319, -4.87439, -6.99651], "size": [2, 4, 9], "inflate": -0.2, "pivot": [22.35319, -2.57439, 1.00349], "rotation": [-4, 5, 0], "uv": [172, 21]}, + {"origin": [25.07945, -5.39939, -1.96459], "size": [2, 3, 12], "inflate": 0.5, "pivot": [22.55081, -8.06987, 13.91722], "rotation": [-2.31336, 4.43385, -27.58957], "uv": [222, 145]}, + {"origin": [23.17945, -0.89939, -0.68959], "size": [2, 3, 12], "inflate": 0.4, "pivot": [20.65081, -3.56987, 15.19222], "rotation": [14.70847, 5.40113, -27.33492], "uv": [222, 130]} + ] + }, + { + "name": "leg_right", + "parent": "Tyrannosaurus", + "pivot": [-20.42923, 70.7324, 8.85434], + "rotation": [-15.09173, 1.85092, 0.08398], + "cubes": [ + {"origin": [-25.41866, 41.02086, -4.25109], "size": [12.875, 42.825, 23.725], "uv": [79, 257], "mirror": true} + ] + }, + { + "name": "leg_right2", + "parent": "leg_right", + "pivot": [-21.99279, 43.52539, 1.69711], + "cubes": [ + {"origin": [-25.244, 24.19389, 3.09297], "size": [12.4, 20.375, 15.95], "pivot": [-21.944, 35.38139, 0.96797], "rotation": [34, 0, 0], "uv": [279, 297], "mirror": true} + ] + }, + { + "name": "leg_right3", + "parent": "leg_right2", + "pivot": [-19.76532, 33.92692, 13.90262], + "rotation": [55, 0, 0], + "cubes": [ + {"origin": [-24.22989, 14.97535, 9.92239], "size": [10, 19, 9], "inflate": -0.1, "uv": [0, 0], "mirror": true} + ] + }, + { + "name": "leg_right4", + "parent": "leg_right3", + "pivot": [-19.76532, 14.92692, 13.90262], + "rotation": [-60, 0, 0], + "cubes": [ + {"origin": [-24.22989, 2.41759, 7.14887], "size": [10, 17, 9], "inflate": 0.375, "uv": [0, 85], "mirror": true} + ] + }, + { + "name": "Claw6", + "parent": "leg_right4", + "pivot": [-18.03922, 11.69239, 7.72125], + "rotation": [3.25353, 39.92899, 4.10228], + "cubes": [ + {"origin": [-21.68942, 7.60244, 18.32118], "size": [2, 5, 6], "inflate": -0.2, "pivot": [-20.68942, 10.90244, 16.32118], "rotation": [4, -5, 0], "uv": [19, 239], "mirror": true}, + {"origin": [-17.96316, 8.07744, 15.28926], "size": [2, 3, 4], "inflate": 0.5, "pivot": [-20.4918, 5.40696, 3.40745], "rotation": [2.31336, -4.43385, -27.58957], "uv": [29, 0], "mirror": true} + ] + }, + { + "name": "leg_right5", + "parent": "leg_right4", + "pivot": [-19.76532, 2.72669, 9.58045], + "rotation": [32.5, 0, 0], + "cubes": [ + {"origin": [-24.22989, -4.78265, 7.2517], "size": [10, 8, 8], "inflate": 0.35, "uv": [42, 267], "mirror": true} + ] + }, + { + "name": "Claw7", + "parent": "leg_right5", + "pivot": [-14.03922, -1.93376, 9.83799], + "rotation": [-11.28559, -36.03256, 29.0792], + "cubes": [ + {"origin": [-17.68942, -5.02371, -6.76194], "size": [2, 4, 9], "inflate": -0.2, "pivot": [-16.68942, -2.72371, 1.23806], "rotation": [-4, 5, 0], "uv": [78, 267], "mirror": true}, + {"origin": [-13.96316, -5.54871, -1.73002], "size": [2, 3, 12], "inflate": 0.5, "pivot": [-16.4918, -8.21919, 14.15179], "rotation": [-2.31336, 4.43385, -27.58957], "uv": [45, 298], "mirror": true}, + {"origin": [-15.86316, -1.04871, -0.45502], "size": [2, 3, 12], "inflate": 0.4, "pivot": [-18.3918, -3.71919, 15.42679], "rotation": [14.70847, 5.40113, -27.33492], "uv": [263, 297], "mirror": true} + ] + }, + { + "name": "Claw8", + "parent": "leg_right5", + "pivot": [-18.00128, -1.52657, 8.77203], + "rotation": [6.46283, -10.46681, 26.49382], + "cubes": [ + {"origin": [-21.65148, -4.61652, -7.8279], "size": [2, 4, 9], "inflate": -0.2, "pivot": [-20.65148, -2.31652, 0.1721], "rotation": [-8.25367, 3.37646, -28.00254], "uv": [259, 183], "mirror": true}, + {"origin": [-17.92522, -5.14152, -2.79598], "size": [2, 3, 12], "inflate": 0.5, "pivot": [-20.45386, -7.812, 13.08583], "rotation": [-2.31336, 4.43385, -27.58957], "uv": [0, 284], "mirror": true}, + {"origin": [-19.82522, -0.64152, -1.52098], "size": [2, 3, 12], "inflate": 0.4, "pivot": [-22.35386, -3.312, 14.36083], "rotation": [14.70847, 5.40113, -27.33492], "uv": [45, 283], "mirror": true} + ] + }, + { + "name": "Claw9", + "parent": "leg_right5", + "pivot": [-25.00339, -1.78444, 9.60342], + "rotation": [-6.62428, 31.83686, -25.40349], + "cubes": [ + {"origin": [-23.35319, -4.87439, -6.99651], "size": [2, 4, 9], "inflate": -0.2, "pivot": [-22.35319, -2.57439, 1.00349], "rotation": [-4, -5, 0], "uv": [172, 21], "mirror": true}, + {"origin": [-27.07945, -5.39939, -1.96459], "size": [2, 3, 12], "inflate": 0.5, "pivot": [-22.55081, -8.06987, 13.91722], "rotation": [-2.31336, -4.43385, 27.58957], "uv": [222, 145], "mirror": true}, + {"origin": [-25.17945, -0.89939, -0.68959], "size": [2, 3, 12], "inflate": 0.4, "pivot": [-20.65081, -3.56987, 15.19222], "rotation": [14.70847, -5.40113, 27.33492], "uv": [222, 130], "mirror": true} + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/neoforge/src/main/resources/assets/bluelib/lang/en_us.json b/neoforge/src/main/resources/assets/bluelib/lang/en_us.json new file mode 100644 index 00000000..8371c823 --- /dev/null +++ b/neoforge/src/main/resources/assets/bluelib/lang/en_us.json @@ -0,0 +1,4 @@ +{ + "entity.bluelib.example_one": "Example", + "entity.bluelib.example_two": "Example" +} \ No newline at end of file diff --git a/neoforge/src/main/resources/assets/bluelib/textures/entity/dragon/blue.png b/neoforge/src/main/resources/assets/bluelib/textures/entity/dragon/blue.png new file mode 100644 index 0000000000000000000000000000000000000000..83be2147b8db3aaab415e12dc3b6e9a11a594ea4 GIT binary patch literal 676 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!7>k44ofvPP)Tsw@I14-?iy0WW zg+Z8+Vb&Z8pkQEtPlzj!7Pn|)P)=iz4q{MmaJHmZ`5KTT zT@vIM%(v z(aRpoCoWyiTgojaD&F|F!PdZVM@UbNjptVBj4jLF?f%O$LFQ-3hJ{yuCnpH{RqTF0 zeUaRrN`{moTZcM{69EVLnQmQO%s%OtOoW(UeeqPr*})2&$=17>Km8Ke@%eU3;;UAl z_dE-lA2QvTo9bV8@y{Lo#EEr$XX6_ew|<>1!7nqtJ8%c@3EiH9*VZm)@~qx!CojNR zaWk9uaEODlE8kU9hhhtcCz6N4bsR43<#29rQagW3>HO}AsXY7mmmhHEtgR04`g-ue zwv#SzoSD}|v`Lv6asM;6d&}sM&)V{9@7_H|f67<|ydtWYcW8+%(7BtgsyBPB&W+HL z{c<<=%Uw8|weJPP@nd2)_peY@i7iud+B+%Zp5z_{-H+UVy3Ca989GZ6MLiBbQ|UWb z(Y^fs4MF?eSt1usd41cW*!(Qw)s%f3B9+u@XC5>Aq_Xz;j^mF{Oz1waD#L%1(lgZ; z3|#E;@8(@%^IqToRn(#6{C~ELFSiAJm_Id{T)EuXZp3k!(UWyjbJC~xst$5b_e=bf q?_)BNT$9h7r)_M&3`GSo`xu+NS2^bO6ubn+JAk44ofvPP)Tsw@I14-?iy0WW zg+Z8+Vb&Z8pkQEtPlzj!&W-dj)XETq4p!hyw%*PB>6gHc&$n9=U$y$Y z=ULGFkm<(URR6k*f9~ifPORfQ8{fFN_3La2ewpdrfjf9l==L1Ewstv_XZ2P)c>&If zo7udFLmZS{`L3Ee6k9MnkvtTx<8Wy&hjW9I+WA{b=XXy`<=Mx-{D3oOZFPXx*MkqX zopgEQ%)BO|P0GxO`=7DhTSkX`)|Ov;_wF(JQ^qRb6;Z{!LrZLd&fRoXz1eGZZiJTX zm%F)N?!wuueJ>b}9}~N|e}$?_Y?+eN-boqvB=;!je&qhsWu|1$&{>iw>T&p)O5eGP z?&bGy2-@$?61i~7>)RH^=4TPFrtI4gsia;z^O)Hum9@`z9DjUbLid4H8UCA;o~gcI z;9{45H}4Xg_xk>?q7EhJ|FdO$xh>$s{He+0%H_s(BaX|Ao~)CalRmvyb&z|yU*ey9 qACrmXntbLwZDRvwC@P5A$Jpe($}z8};3Y8L89ZJ6T-G@yGywpxb1(q_ literal 0 HcmV?d00001 diff --git a/neoforge/src/main/resources/assets/bluelib/textures/entity/dragon/dark.png b/neoforge/src/main/resources/assets/bluelib/textures/entity/dragon/dark.png new file mode 100644 index 0000000000000000000000000000000000000000..56c9452f64ef0b1e71cecef3b4d07f56ae847633 GIT binary patch literal 676 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!7>k44ofvPP)Tsw@I14-?iy0WW zg+Z8+Vb&Z8pkQEtPlzj!mKPJ^=Hz5!W#wdNPn^2p^s67*vJwUgQb~?#2X}7uGm#gP zQ06^oDwgx$fq}uR<%$^@8TFMVidv2~UU@d+Y|c^~DgvxPb0$Wc7}!LuS~IT`$dN7y z@(X78j}sipEHVW;@~EeaV@SoEw^wiHEjAEfdvHHFiDPf3^4f2I61s;NV<1;Z1`L*Y6Om-ccvH#n)Czom43_rz45ef-N0ICIuk2Y7uw_+Z;f zmp9JLYa-gD%#67I8QZ;ObjW9I`L%cN9-}{HtO8yURm?lI#1`n>O;^>My;kQ&Xvu!L zoBQQ1oXy(zg5mfvv77r>sH()4DLL((lyOgTkAm(;?mt~-O7;w$C5fUQho7nRovY|x ze*cD`{q8K03#YukZBcA~7V&Dzz73H|>a{bEnSD}O`+Udo$0sIqA6S*)ze(wt>I()g zcKLVnF0py9@Bb?5P;&l1TgI2$0zS;2noO=-ZfrN=xXkFuI;lD7(|c71xu^Rj{>k?- onMkh5XU@|$HeiOLf|z}bP2Q^<^Lh$i0^^;*)78&qol`;+0O_S2&j0`b literal 0 HcmV?d00001 diff --git a/neoforge/src/main/resources/assets/bluelib/textures/entity/dragon/pink.png b/neoforge/src/main/resources/assets/bluelib/textures/entity/dragon/pink.png new file mode 100644 index 0000000000000000000000000000000000000000..adbd4aba3aedfc3c346c7fcbcd858cbe4dd261e6 GIT binary patch literal 676 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!7>k44ofvPP)Tsw@I14-?iy0WW zg+Z8+Vb&Z8pkQEtPlzj!Zjwt65OA~RHu2!IKd|xH`;UJotCY^rs5}_f`{vw>EiO$N z3LOzw<`r~)`(`)e=Yh7PEk`d-JKbu&a%s|urSjIR6`Z;x41ng`DxW(eV9(;TfP5fF zx+KUinBhN8a3r(H6zIsKo-U3d6?5KRy`8t%K!EMR{p2K$y_w2uzx|D07t}vHbT_ZS zqnACFPh7g3x0G8Pb3e8>o{E6%i-MMq;~$6()ryJQ+f9BFF)YSSz8_8_4VL` zZ6{scI5V$_Xp=HC;{IoB_m!M8-n(`vqUbO^7^(#vH4lVt10_7L@KG*&OB!JNoDQx9mgM^n9zM-Rfhj2rDv)y z7`WKw-_5(k=DoiEtEfZC`TuMgUv3NdFn?+?xpKL&-H78dqbKX6=A=*WRUPD>?w9x{ q-^XMkxh9`EPutjl8Hx&G_AxejuX4=mDR>EtcLq;aKbLh*2~7ZCIW7SJ literal 0 HcmV?d00001 diff --git a/neoforge/src/main/resources/assets/bluelib/textures/entity/rex/brown.png b/neoforge/src/main/resources/assets/bluelib/textures/entity/rex/brown.png new file mode 100644 index 0000000000000000000000000000000000000000..f2956d0504901ccd33fb2067b7631ba08471c5ce GIT binary patch literal 3454 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 or57IkfgP>p|9}4H3?K* zd$|Vy0Lxt%z&JT@wO!)n0A&PtxcTWHMQ;CCUK+VbR@PgIE_Q-#4VZ+o>m>4&1Ni!+e7d%l_n? zxZ~ZcB#F9yLg{*Zse~b2F@UYst7MK&{iUQ_aG5EvBgvGNXSSOn&)a!Pwl4YxCIRZr zrAF;ib-vH8++2$fyhbC(n*6E`H_m9e5j)dx#4vTTIM(N7SJ&DT!#AwbRgg-JQwN5Q zx5emxTfD z<_{FgIf}z3c0d4#tD#w9mk5BOP9zjaEKzEgPdNzdq+Fv8RiifJ6`& zvx`(knW5m^Q7YzIDsK%yoEEKP*to+aayaWS%U7eh^5KPKEi0l+qpb>Sj3-;5;D+Kv zHlYL-i|VI}v!0##+8Tmz3N3|4)Zp-ePN_qE;D;FvD1ULzXKSxNHqSj&!)yt!U%(h8{>l>X*3G z4Q~FW@*G8)!++>u?i3VOvVR^=a}as+}U{ny5{`LCt;Y zkO{jf(STt?eR~A|C|L*A`=Jo@0c(8qWjZ!ts%wf4*qe=-BnD9I5Y1D}^z#BBF%pE) zl>}R84GSTDkh!q8L5V|p^W~sxAbO7}5@Gd2@dack6VEW$o1FSADwp;WBA#Fqs{UVX zGZnHihI?Q>wI2@j+hNz8O|0QYC41S$o%0jx; z@W29W8|&NuB)_Qaq9M|4Cg#m1olT!j+qyepp4OlgNrC8VrMEU={pUVObBMHjgc1wK zsq{7j52I5RW`nJ7;`ubl1cmNWI+;L9FSj+VU+T^N)LJM6Wp)lVs1y}8XHa_g~ z&Nvc@>7Bm1^WT@q#QUH2gpsl8jp!=!4$+J{6e2PuRC{AZAFtJoti}vQQz9%93)Jv@ zyFFLtJ?~V7jRsa)!wh|m--{awAi{odkjHLIAH4c(g@i>XrbqcZ2>P$-ncZB%-wva( zD?5>%sFYVXnp|OD@5wc&2RgL@X^*t^l|mto*0ZWOJ8mRKs-j|<=sELnkVIrOmN-@@ zLrD-VE0y(+ioeT_yL5E1rnaO3PxCqFpB1&`%C2%X+L$T~1m&1