├── CHANGELOG.md ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── common ├── src │ └── main │ │ ├── resources │ │ ├── pack.mcmeta │ │ ├── forgivingvoid.png │ │ ├── forgivingvoid.mixins.json │ │ └── assets │ │ │ └── forgivingvoid │ │ │ └── lang │ │ │ ├── ko_kr.json │ │ │ ├── es_es.json │ │ │ ├── tr_tr.json │ │ │ └── en_us.json │ │ └── java │ │ └── net │ │ └── blay09 │ │ └── mods │ │ └── forgivingvoid │ │ ├── mixin │ │ ├── ServerPlayerAccessor.java │ │ ├── ServerGamePacketListenerImplAccessor.java │ │ ├── ThrownTridentAccessor.java │ │ └── CombatTrackerMixin.java │ │ ├── DamageOnFallMode.java │ │ ├── ForgivingVoidFallThroughEvent.java │ │ ├── ForgivingVoidConfig.java │ │ └── ForgivingVoid.java ├── dependencies.gradle └── build.gradle ├── forge ├── dependencies.gradle ├── src │ └── main │ │ ├── resources │ │ ├── forgivingvoid.forge.mixins.json │ │ └── META-INF │ │ │ └── mods.toml │ │ └── java │ │ └── net │ │ └── blay09 │ │ └── mods │ │ └── forgivingvoid │ │ └── ForgeForgivingVoid.java └── build.gradle ├── fabric ├── dependencies.gradle ├── src │ └── main │ │ ├── resources │ │ ├── forgivingvoid.fabric.mixins.json │ │ └── fabric.mod.json │ │ └── java │ │ └── net │ │ └── blay09 │ │ └── mods │ │ └── forgivingvoid │ │ └── fabric │ │ └── FabricForgivingVoid.java └── build.gradle ├── neoforge ├── dependencies.gradle ├── src │ └── main │ │ ├── resources │ │ ├── forgivingvoid.neoforge.mixins.json │ │ └── META-INF │ │ │ └── neoforge.mods.toml │ │ └── java │ │ └── net │ │ └── blay09 │ │ └── mods │ │ └── forgivingvoid │ │ └── NeoForgeForgivingVoid.java └── build.gradle ├── .gitattributes ├── .gitignore ├── LICENSE ├── .github ├── workflows │ ├── manage-labels.yaml │ ├── label-issues.yaml │ ├── publish-snapshot.yml │ └── publish-release.yml └── advanced-issue-labeler.yml ├── README.md ├── settings.gradle ├── modpage.md ├── gradle.properties ├── gradlew.bat ├── repositories.gradle └── gradlew /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | - Updated to Minecraft 1.21.11 -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwelveIterationMods/ForgivingVoid/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /common/src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "description": "${mod_name}", 4 | "pack_format": ${pack_format_number} 5 | } 6 | } -------------------------------------------------------------------------------- /common/src/main/resources/forgivingvoid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwelveIterationMods/ForgivingVoid/HEAD/common/src/main/resources/forgivingvoid.png -------------------------------------------------------------------------------- /common/dependencies.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation(libs.balmCommon) { 3 | changing = libs.versions.balm.get().endsWith("SNAPSHOT") 4 | } 5 | } -------------------------------------------------------------------------------- /forge/dependencies.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation(libs.balmForge) { 3 | changing = libs.versions.balm.get().endsWith("SNAPSHOT") 4 | } 5 | } -------------------------------------------------------------------------------- /fabric/dependencies.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | modImplementation(libs.balmFabric) { 3 | changing = libs.versions.balm.get().endsWith("SNAPSHOT") 4 | } 5 | } -------------------------------------------------------------------------------- /neoforge/dependencies.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation(libs.balmNeoForge) { 3 | changing = libs.versions.balm.get().endsWith("SNAPSHOT") 4 | } 5 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | *.bat text eol=crlf 3 | *.patch text eol=lf 4 | *.java text eol=lf 5 | *.gradle text eol=crlf 6 | *.png binary 7 | *.gif binary 8 | *.exe binary 9 | *.dll binary 10 | *.jar binary 11 | *.lzma binary 12 | *.zip binary 13 | *.pyd binary 14 | *.cfg text eol=lf 15 | *.jks binary 16 | *.ogg binary -------------------------------------------------------------------------------- /forge/src/main/resources/forgivingvoid.forge.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "net.blay09.mods.forgivingvoid.forge.mixin", 5 | "compatibilityLevel": "JAVA_17", 6 | "mixins": [ 7 | ], 8 | "client": [ 9 | ], 10 | "injectors": { 11 | "defaultRequire": 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # eclipse 2 | bin 3 | *.launch 4 | .eclipse 5 | .settings 6 | .metadata 7 | .classpath 8 | .project 9 | 10 | # idea 11 | out 12 | *.ipr 13 | *.iws 14 | *.iml 15 | .idea 16 | 17 | # gradle 18 | build 19 | .gradle 20 | 21 | # other 22 | eclipse 23 | run 24 | runs 25 | runserver 26 | logs 27 | 28 | common/src/generated/resources/.cache -------------------------------------------------------------------------------- /neoforge/src/main/resources/forgivingvoid.neoforge.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "net.blay09.mods.forgivingvoid.neoforge.mixin", 5 | "compatibilityLevel": "JAVA_17", 6 | "mixins": [ 7 | ], 8 | "client": [ 9 | ], 10 | "injectors": { 11 | "defaultRequire": 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /fabric/src/main/resources/forgivingvoid.fabric.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "net.blay09.mods.forgivingvoid.fabric.mixin", 5 | "refmap": "${mod_id}.refmap.json", 6 | "compatibilityLevel": "JAVA_17", 7 | "mixins": [ 8 | ], 9 | "client": [ 10 | ], 11 | "injectors": { 12 | "defaultRequire": 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /common/src/main/java/net/blay09/mods/forgivingvoid/mixin/ServerPlayerAccessor.java: -------------------------------------------------------------------------------- 1 | package net.blay09.mods.forgivingvoid.mixin; 2 | 3 | import net.minecraft.server.level.ServerPlayer; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.gen.Accessor; 6 | 7 | @Mixin(ServerPlayer.class) 8 | public interface ServerPlayerAccessor { 9 | 10 | @Accessor 11 | void setIsChangingDimension(boolean isChangingDimension); 12 | } 13 | -------------------------------------------------------------------------------- /common/src/main/java/net/blay09/mods/forgivingvoid/DamageOnFallMode.java: -------------------------------------------------------------------------------- 1 | package net.blay09.mods.forgivingvoid; 2 | 3 | import net.minecraft.util.StringRepresentable; 4 | 5 | import java.util.Locale; 6 | 7 | public enum DamageOnFallMode implements StringRepresentable { 8 | ABSOLUTE, 9 | RELATIVE_CURRENT, 10 | RELATIVE_MAX; 11 | 12 | @Override 13 | public String getSerializedName() { 14 | return name().toLowerCase(Locale.ROOT); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /common/src/main/resources/forgivingvoid.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "net.blay09.mods.forgivingvoid.mixin", 5 | "refmap": "${mod_id}.refmap.json", 6 | "compatibilityLevel": "JAVA_17", 7 | "mixins": [ 8 | "ServerGamePacketListenerImplAccessor", 9 | "ServerPlayerAccessor", 10 | "ThrownTridentAccessor", 11 | "CombatTrackerMixin" 12 | ], 13 | "client": [ 14 | ], 15 | "injectors": { 16 | "defaultRequire": 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /common/src/main/java/net/blay09/mods/forgivingvoid/mixin/ServerGamePacketListenerImplAccessor.java: -------------------------------------------------------------------------------- 1 | package net.blay09.mods.forgivingvoid.mixin; 2 | 3 | import net.minecraft.server.network.ServerGamePacketListenerImpl; 4 | import net.minecraft.world.phys.Vec3; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | @Mixin(ServerGamePacketListenerImpl.class) 9 | public interface ServerGamePacketListenerImplAccessor { 10 | @Accessor 11 | Vec3 getAwaitingPositionFromClient(); 12 | } 13 | -------------------------------------------------------------------------------- /common/src/main/java/net/blay09/mods/forgivingvoid/mixin/ThrownTridentAccessor.java: -------------------------------------------------------------------------------- 1 | package net.blay09.mods.forgivingvoid.mixin; 2 | 3 | import net.minecraft.network.syncher.EntityDataAccessor; 4 | import net.minecraft.world.entity.projectile.arrow.ThrownTrident; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | @Mixin(ThrownTrident.class) 9 | public interface ThrownTridentAccessor { 10 | 11 | @Accessor("ID_LOYALTY") 12 | static EntityDataAccessor getIdLoyalty() { 13 | throw new AssertionError(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /fabric/src/main/java/net/blay09/mods/forgivingvoid/fabric/FabricForgivingVoid.java: -------------------------------------------------------------------------------- 1 | package net.blay09.mods.forgivingvoid.fabric; 2 | 3 | import net.blay09.mods.balm.Balm; 4 | import net.blay09.mods.balm.fabric.platform.runtime.FabricLoadContext; 5 | import net.blay09.mods.forgivingvoid.ForgivingVoid; 6 | import net.fabricmc.api.ModInitializer; 7 | 8 | public class FabricForgivingVoid implements ModInitializer { 9 | @Override 10 | public void onInitialize() { 11 | Balm.initializeMod(ForgivingVoid.MOD_ID, FabricLoadContext.INSTANCE, ForgivingVoid::initialize); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | All Rights Reserved 2 | 3 | Copyright (c) 2023 BlayTheNinth 4 | 5 | For modpack permissions and other exceptions, see https://mods.twelveiterations.com/permissions 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 8 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 9 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 10 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | -------------------------------------------------------------------------------- /neoforge/src/main/java/net/blay09/mods/forgivingvoid/NeoForgeForgivingVoid.java: -------------------------------------------------------------------------------- 1 | package net.blay09.mods.forgivingvoid; 2 | 3 | import net.blay09.mods.balm.Balm; 4 | import net.blay09.mods.balm.neoforge.platform.runtime.NeoForgeLoadContext; 5 | import net.neoforged.bus.api.IEventBus; 6 | import net.neoforged.fml.common.Mod; 7 | 8 | @Mod(ForgivingVoid.MOD_ID) 9 | public class NeoForgeForgivingVoid { 10 | 11 | public NeoForgeForgivingVoid(IEventBus modEventBus) { 12 | final var context = new NeoForgeLoadContext(modEventBus); 13 | Balm.initializeMod(ForgivingVoid.MOD_ID, context, ForgivingVoid::initialize); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /common/src/main/resources/assets/forgivingvoid/lang/ko_kr.json: -------------------------------------------------------------------------------- 1 | { 2 | "forgivingvoid.configuration.title": "너그러운 빈 공간", 3 | "forgivingvoid.configuration.triggerAtY": "Y에서 트리거", 4 | "forgivingvoid.configuration.damageOnFall": "추락 피해", 5 | "forgivingvoid.configuration.fallingHeight": "낙하 높이", 6 | "forgivingvoid.configuration.preventDeath": "사망 방지", 7 | "forgivingvoid.configuration.triggerInOverworld": "오버월드에서 트리거", 8 | "forgivingvoid.configuration.triggerInNether": "네더에서 트리거", 9 | "forgivingvoid.configuration.triggerInEnd": "엔드에서 트리거", 10 | "forgivingvoid.configuration.disableVanillaAntiCheatWhileFalling": "추락하는 동안 바닐라 안티치트 비활성화", 11 | "forgivingvoid.configuration.dimensionAllowList": "차원 허용 목록", 12 | "forgivingvoid.configuration.dimensionDenyList": "차원 거부 목록" 13 | } -------------------------------------------------------------------------------- /forge/src/main/resources/META-INF/mods.toml: -------------------------------------------------------------------------------- 1 | modLoader="javafml" 2 | loaderVersion="*" 3 | license="${license}" 4 | issueTrackerURL="${issues}" 5 | [[mods]] 6 | modId="${mod_id}" 7 | version="${version}" 8 | displayName="${mod_name}" 9 | displayURL="${homepage}" 10 | logoFile="${mod_id}.png" 11 | credits="BlayTheNinth" 12 | authors="BlayTheNinth" 13 | description='''${description}''' 14 | [[dependencies.${mod_id}]] 15 | modId="forge" 16 | mandatory=true 17 | versionRange="[${forge_version},)" 18 | ordering="NONE" 19 | side="BOTH" 20 | [[dependencies.${mod_id}]] 21 | modId="minecraft" 22 | mandatory=true 23 | versionRange="[${minecraft_version},)" 24 | ordering="NONE" 25 | side="BOTH" 26 | [[dependencies.${mod_id}]] 27 | modId="balm" 28 | mandatory=true 29 | versionRange="[${balm_version},)" 30 | ordering="NONE" 31 | side="BOTH" 32 | -------------------------------------------------------------------------------- /forge/src/main/java/net/blay09/mods/forgivingvoid/ForgeForgivingVoid.java: -------------------------------------------------------------------------------- 1 | package net.blay09.mods.forgivingvoid; 2 | 3 | import net.blay09.mods.balm.Balm; 4 | import net.blay09.mods.balm.forge.platform.runtime.ForgeLoadContext; 5 | import net.minecraftforge.fml.IExtensionPoint; 6 | import net.minecraftforge.fml.common.Mod; 7 | import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; 8 | 9 | @Mod(ForgivingVoid.MOD_ID) 10 | public class ForgeForgivingVoid { 11 | 12 | public ForgeForgivingVoid(FMLJavaModLoadingContext context) { 13 | final var loadContext = new ForgeLoadContext(context.getModBusGroup()); 14 | Balm.initializeMod(ForgivingVoid.MOD_ID, loadContext, ForgivingVoid::initialize); 15 | 16 | context.registerDisplayTest(IExtensionPoint.DisplayTest.IGNORE_ALL_VERSION); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /common/src/main/resources/assets/forgivingvoid/lang/es_es.json: -------------------------------------------------------------------------------- 1 | { 2 | "forgivingvoid.configuration.title": "Vacío piadoso", 3 | "forgivingvoid.configuration.triggerAtY": "Activar en Y", 4 | "forgivingvoid.configuration.damageOnFall": "Daño al caer", 5 | "forgivingvoid.configuration.fallingHeight": "Altura de caída", 6 | "forgivingvoid.configuration.preventDeath": "Prevenir muerte", 7 | "forgivingvoid.configuration.triggerInOverworld": "Activar en la superficie", 8 | "forgivingvoid.configuration.triggerInNether": "Activar en el Nether", 9 | "forgivingvoid.configuration.triggerInEnd": "Activar en el End", 10 | "forgivingvoid.configuration.disableVanillaAntiCheatWhileFalling": "Desactivar el antitrampa predeterminado al caer", 11 | "forgivingvoid.configuration.dimensionAllowList": "Lista de dimensiones permitidas", 12 | "forgivingvoid.configuration.dimensionDenyList": "Lista de dimensiones denegadas" 13 | } 14 | -------------------------------------------------------------------------------- /neoforge/src/main/resources/META-INF/neoforge.mods.toml: -------------------------------------------------------------------------------- 1 | modLoader="javafml" 2 | loaderVersion="[1,)" 3 | license="${license}" 4 | issueTrackerURL="${issues}" 5 | [[mods]] 6 | modId="${mod_id}" 7 | version="${version}" 8 | displayName="${mod_name}" 9 | displayURL="${homepage}" 10 | displayTest="NONE" 11 | logoFile="${mod_id}.png" 12 | credits="BlayTheNinth" 13 | authors="BlayTheNinth" 14 | description='''${description}''' 15 | [[mixins]] 16 | config = "${mod_id}.mixins.json" 17 | [[mixins]] 18 | config = "${mod_id}.neoforge.mixins.json" 19 | [[dependencies.${mod_id}]] 20 | modId="neoforge" 21 | type="required" 22 | versionRange="[${neoforge_version},)" 23 | ordering="NONE" 24 | side="BOTH" 25 | [[dependencies.${mod_id}]] 26 | modId="minecraft" 27 | type="required" 28 | versionRange="[${minecraft_version},)" 29 | ordering="NONE" 30 | side="BOTH" 31 | [[dependencies.${mod_id}]] 32 | modId="balm" 33 | type="required" 34 | versionRange="[${balm_version},)" 35 | ordering="NONE" 36 | side="BOTH" 37 | -------------------------------------------------------------------------------- /.github/workflows/manage-labels.yaml: -------------------------------------------------------------------------------- 1 | name: manage-labels 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | dry: 6 | description: 'Dry run (no changes, log only)' 7 | required: false 8 | default: true 9 | type: boolean 10 | remove_missing: 11 | description: 'Remove labels not present in the source data' 12 | required: false 13 | default: false 14 | type: boolean 15 | jobs: 16 | manage-labels: 17 | permissions: 18 | contents: read 19 | issues: write 20 | runs-on: ubuntu-latest 21 | name: manage-labels 22 | steps: 23 | - uses: actions/checkout@v2 24 | - uses: TwelveIterations/manage-labels@main 25 | with: 26 | dry: ${{ inputs.dry }} 27 | remove_missing: ${{ inputs.remove_missing }} 28 | source: https://raw.githubusercontent.com/TwelveIterationMods/.github/refs/heads/main/labels.yaml 29 | env: 30 | GITHUB_TOKEN: ${{ github.token }} 31 | -------------------------------------------------------------------------------- /.github/advanced-issue-labeler.yml: -------------------------------------------------------------------------------- 1 | policy: 2 | - template: [report-a-bug.yml] 3 | section: 4 | - id: [minecraftVersion] 5 | block-list: [other] 6 | label: 7 | - name: Minecraft 1.21.11 8 | keys: ['1.21.11'] 9 | - name: Minecraft 1.21.10 10 | keys: ['1.21.10'] 11 | - name: Minecraft 1.21.8 12 | keys: ['1.21.8'] 13 | - name: Minecraft 1.21.5 14 | keys: ['1.21.5'] 15 | - name: Minecraft 1.21.4 16 | keys: ['1.21.4'] 17 | - name: Minecraft 1.21.1 18 | keys: ['1.21.1 (LTS)'] 19 | - name: Minecraft 1.20.1 20 | keys: ['1.20.1 (LTS)'] 21 | - name: EOL 22 | keys: ['Other (specify below)'] 23 | - id: [modLoader] 24 | label: 25 | - name: NeoForge 26 | keys: ['NeoForge'] 27 | - name: Fabric 28 | keys: ['Fabric'] 29 | - name: 'Forge' 30 | keys: ['Forge'] 31 | -------------------------------------------------------------------------------- /common/src/main/java/net/blay09/mods/forgivingvoid/ForgivingVoidFallThroughEvent.java: -------------------------------------------------------------------------------- 1 | package net.blay09.mods.forgivingvoid; 2 | 3 | import net.blay09.mods.balm.platform.event.BidirectionalEventMapper; 4 | import net.blay09.mods.balm.platform.event.EventMapper; 5 | import net.minecraft.world.entity.Entity; 6 | 7 | import java.util.function.Consumer; 8 | 9 | public class ForgivingVoidFallThroughEvent { 10 | 11 | public static final BidirectionalEventMapper> EVENT = EventMapper.createBound(ForgivingVoidFallThroughEvent.class); 12 | 13 | private final Entity entity; 14 | private boolean canceled; 15 | 16 | public ForgivingVoidFallThroughEvent(Entity entity) { 17 | this.entity = entity; 18 | } 19 | 20 | public Entity getEntity() { 21 | return entity; 22 | } 23 | 24 | public boolean isCanceled() { 25 | return canceled; 26 | } 27 | 28 | public void setCanceled(boolean canceled) { 29 | this.canceled = canceled; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/label-issues.yaml: -------------------------------------------------------------------------------- 1 | name: Label Issues 2 | on: 3 | issues: 4 | types: [ opened ] 5 | jobs: 6 | label-component: 7 | runs-on: ubuntu-latest 8 | permissions: 9 | contents: read 10 | issues: write 11 | strategy: 12 | matrix: 13 | template: [ report-a-bug.yml ] 14 | steps: 15 | - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 16 | 17 | - name: Parse issue form 18 | uses: TwelveIterations/github-issue-parser@main 19 | id: issue-parser 20 | with: 21 | template-path: https://raw.githubusercontent.com/TwelveIterationMods/.github/refs/heads/main/.github/ISSUE_TEMPLATE/${{ matrix.template }} 22 | 23 | - name: Set labels based on component field 24 | uses: redhat-plumbers-in-action/advanced-issue-labeler@d498805e5c7c0658e336948b3363480bcfd68da6 25 | with: 26 | issue-form: ${{ steps.issue-parser.outputs.jsonString }} 27 | template: ${{ matrix.template }} 28 | token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Forgiving Void 2 | 3 | Minecraft Mod. Makes the void hate you a little less (and vice-versa). Fall down and come back out on top. 4 | 5 | - [Modpack Permissions](https://mods.twelveiterations.com/permissions) 6 | 7 | #### Downloads 8 | 9 | [![Versions](http://cf.way2muchnoise.eu/versions/271009_latest.svg)](https://minecraft.curseforge.com/projects/forgiving-void) [![Downloads](http://cf.way2muchnoise.eu/full_271009_downloads.svg)](https://minecraft.curseforge.com/projects/forgiving-void) 10 | 11 | ## Contributing 12 | 13 | If you're interested in contributing to the mod, you can check out [issues labelled as "help wanted"](https://github.com/TwelveIterationMods/ForgivingVoid/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22). 14 | 15 | When it comes to new features, it's best to confer with me first to ensure we share the same vision. You can join us on [Discord](https://discord.gg/VAfZ2Nau6j) if you'd like to talk. 16 | 17 | Contributions must be done through pull requests. I will not be able to accept translations, code or other assets through any other channels. 18 | -------------------------------------------------------------------------------- /fabric/src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "${mod_id}", 4 | "version": "${version}", 5 | 6 | "name": "${mod_name}", 7 | "description": "${description}", 8 | "authors": [ 9 | "BlayTheNinth" 10 | ], 11 | "contact": { 12 | "homepage": "${homepage}", 13 | "sources": "${sources}", 14 | "issues": "${issues}" 15 | }, 16 | 17 | "license": "${license}", 18 | "icon": "${mod_id}.png", 19 | 20 | "environment": "*", 21 | "entrypoints": { 22 | "main": [ 23 | "net.blay09.mods.forgivingvoid.fabric.FabricForgivingVoid" 24 | ], 25 | "client": [ 26 | ] 27 | }, 28 | "mixins": [ 29 | "forgivingvoid.mixins.json", 30 | "forgivingvoid.fabric.mixins.json" 31 | ], 32 | 33 | "depends": { 34 | "fabricloader": ">=${fabric_loader_version}", 35 | "fabric-api": "*", 36 | "balm-fabric": ">=${balm_version}", 37 | "minecraft": ">=${minecraft_version}", 38 | "java": ">=${java_version}" 39 | }, 40 | "suggests": { 41 | }, 42 | "custom": { 43 | "modmenu": { 44 | "links": { 45 | "modmenu.discord": "https://discord.gg/VAfZ2Nau6j" 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | mavenCentral() 5 | exclusiveContent { 6 | forRepository { 7 | maven { 8 | name = 'Fabric' 9 | url = uri("https://maven.fabricmc.net") 10 | } 11 | } 12 | filter { 13 | includeGroupAndSubgroups("net.fabricmc") 14 | includeGroup("fabric-loom") 15 | } 16 | } 17 | exclusiveContent { 18 | forRepository { 19 | maven { 20 | name = 'Sponge' 21 | url = uri('https://repo.spongepowered.org/repository/maven-public') 22 | } 23 | } 24 | filter { 25 | includeGroupAndSubgroups("org.spongepowered") 26 | } 27 | } 28 | exclusiveContent { 29 | forRepository { 30 | maven { 31 | name = 'Forge' 32 | url = uri("https://maven.minecraftforge.net") 33 | } 34 | } 35 | filter { 36 | includeGroupAndSubgroups("net.minecraftforge") 37 | } 38 | } 39 | } 40 | } 41 | 42 | plugins { 43 | id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0' 44 | } 45 | 46 | includeBuild('build-logic') 47 | include('common', 'fabric', 'neoforge', 'forge') 48 | -------------------------------------------------------------------------------- /common/src/main/java/net/blay09/mods/forgivingvoid/mixin/CombatTrackerMixin.java: -------------------------------------------------------------------------------- 1 | package net.blay09.mods.forgivingvoid.mixin; 2 | 3 | import net.blay09.mods.balm.Balm; 4 | import net.minecraft.network.chat.Component; 5 | import net.minecraft.world.damagesource.CombatEntry; 6 | import net.minecraft.world.damagesource.CombatTracker; 7 | import net.minecraft.world.entity.Entity; 8 | import net.minecraft.world.entity.LivingEntity; 9 | import org.jetbrains.annotations.Nullable; 10 | import org.spongepowered.asm.mixin.Final; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.Shadow; 13 | import org.spongepowered.asm.mixin.injection.At; 14 | import org.spongepowered.asm.mixin.injection.Inject; 15 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 16 | 17 | @Mixin(CombatTracker.class) 18 | public class CombatTrackerMixin { 19 | 20 | @Shadow 21 | @Final 22 | private LivingEntity mob; 23 | 24 | @Inject(method = "getFallMessage(Lnet/minecraft/world/damagesource/CombatEntry;Lnet/minecraft/world/entity/Entity;)Lnet/minecraft/network/chat/Component;", at = @At("HEAD"), cancellable = true) 25 | public void getFallMessage(CombatEntry combatEntry, @Nullable Entity entity, CallbackInfoReturnable callbackInfoReturnable) { 26 | final var persistentData = Balm.hooks().getPersistentData(mob); 27 | if (persistentData.getBooleanOr("ForgivingVoidIsFalling", false)) { 28 | callbackInfoReturnable.setReturnValue(Component.translatable("death.fell.forgivingvoid", mob.getDisplayName())); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | balm = "21.11.2" 3 | minecraft = "1.21.11" 4 | neoForm = "1.21.11-20251209.172050" 5 | neoForge = "21.11.0-beta" 6 | forge = "61.0.3" 7 | fabricApi = "0.139.4+1.21.11" 8 | fabricLoader = "0.18.1" 9 | 10 | [libraries] 11 | minecraft = { module = "com.mojang:minecraft", version.ref = "minecraft" } 12 | parchment = { module = "org.parchmentmc.data:parchment-1.21.10", version = "2025.10.12" } 13 | 14 | balmCommon = { module = "net.blay09.mods:balm-common", version.ref = "balm" } 15 | balmNeoForge = { module = "net.blay09.mods:balm-neoforge", version.ref = "balm" } 16 | balmForge = { module = "net.blay09.mods:balm-forge", version.ref = "balm" } 17 | balmFabric = { module = "net.blay09.mods:balm-fabric", version.ref = "balm" } 18 | 19 | neoForm = { module = "net.neoforged:neoform", version.ref = "neoForm" } 20 | neoForge = { module = "net.neoforged:neoforge", version.ref = "neoForge" } 21 | forge = { module = "net.minecraftforge:forge", version.ref = "forge" } 22 | fabricApi = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabricApi" } 23 | fabricLoader = { module = "net.fabricmc:fabric-loader", version.ref = "fabricLoader" } 24 | 25 | mixin = { module = "org.spongepowered:mixin", version = "0.8.7" } 26 | 27 | [plugins] 28 | fabricLoom = { id = "fabric-loom", version = "1.13-SNAPSHOT" } 29 | modDevGradle = { id = "net.neoforged.moddev", version = "2.0.107" } 30 | forgeGradle = { id = "net.minecraftforge.gradle", version = "[6.0.25,6.2)" } 31 | mixin = { id = "org.spongepowered.mixin", version = "0.7-SNAPSHOT" } 32 | curseForgeGradle = { id = "net.darkhax.curseforgegradle", version = "1.1.26" } 33 | modrinthMinotaur = { id = "com.modrinth.minotaur", version = "2.+" } 34 | -------------------------------------------------------------------------------- /modpage.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Requires Balm 4 | 5 | 6 | 7 | Become a Patron 8 | 9 | 10 | 11 | Follow me on Twitter 12 | 13 | 14 | Join our Discord 15 | 16 |

17 | 18 | ![](https://blay09.net/files/brand/forgivingvoid.png) 19 | 20 | Have you ever wondered what would happen if instead of dying in the void, you just kept falling? Turns out you just come back out from the top (and the fall doesn't even kill you due to magic miracle power). 21 | 22 | Instead of dying in the void, you will fall back down from the sky. Despite the large falling height, you can still survive as long as you had full health when landing. 23 | 24 | ## Features 25 | 26 | - Fall back down from the sky when falling into the void 27 | - Always survive the fall as long as you had full health 28 | 29 | [Video of Forgiving Void in Action](https://twitter.com/BlayTheNinth/status/880510912762925056) 30 | 31 | ![](https://blay09.net/files/brand/forgivingvoid.gif) -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Mod 2 | mod_id = forgivingvoid 3 | mod_name = Forgiving Void 4 | mod_main=ForgivingVoid 5 | description=Makes the void hate you a little less (and vice-versa). Fall down and come back out on top. 6 | version = 21.11.2 7 | group = net.blay09.mods 8 | homepage=https://mods.twelveiterations.com/mc/forgiving-void 9 | sources=https://github.com/TwelveIterationMods/ForgivingVoid 10 | issues=https://github.com/TwelveIterationMods/ForgivingVoid/issues 11 | discord = https://discord.gg/VAfZ2Nau6j 12 | license=All Rights Reserved 13 | 14 | # Publishing 15 | curseforge_project_id = 271009 16 | curseforge_environments = Server 17 | modrinth_project_id = 1vkzEZjE 18 | maven_releases = https://maven.twelveiterations.com/repository/maven-releases/ 19 | maven_snapshots = https://maven.twelveiterations.com/repository/maven-snapshots/ 20 | 21 | # Minecraft 22 | minecraft_version = 1.21.11-pre1 23 | minecraft_versions = 1.21.11 24 | minecraft_version_range = [1.21.11-beta.1,) 25 | pack_format_number = 64 26 | java_version = 21 27 | 28 | # Balm 29 | balm_version = 21.11.1-SNAPSHOT 30 | balm_version_range = [21.11.0,) 31 | 32 | # Forge 33 | forge_version = 60.0.0 34 | forge_version_range = [60,) 35 | forge_loader_version_range = [60,) 36 | 37 | # NeoForge 38 | neoforge_snapshot_url = 39 | neoforge_version = 21.10.38-beta 40 | neoforge_version_range = [21,) 41 | neoforge_loader_version_range = [1,) 42 | 43 | # Fabric 44 | fabric_version = 0.139.1+1.21.11 45 | fabric_loader_version = 0.17.3 46 | 47 | # Dependencies 48 | mixin_version=0.8.5 49 | modmenu_version=9.0.0 50 | 51 | # Gradle 52 | org.gradle.jvmargs=-Xmx3G 53 | org.gradle.daemon=false 54 | mod_author = BlayTheNinth 55 | credits = BlayTheNinth 56 | kuma_version = 21.11.2 57 | neo_form_version = 1.21.11-pre1-20251119.112005 58 | parchment_minecraft = 1.21.10 59 | parchment_version = 2025.10.12 60 | kuma_version_range = [21.11,21.12) -------------------------------------------------------------------------------- /common/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'multiloader-common' 3 | alias libs.plugins.modDevGradle 4 | } 5 | 6 | neoForge { 7 | neoFormVersion = libs.neoForm.get().version 8 | // Automatically enable AccessTransformers if the file exists 9 | def at = file('src/main/resources/META-INF/accesstransformer.cfg') 10 | if (at.exists()) { 11 | accessTransformers.from(at.absolutePath) 12 | } 13 | parchment.parchmentArtifact = libs.parchment.get().toString() 14 | } 15 | 16 | dependencies { 17 | compileOnly libs.mixin 18 | } 19 | 20 | apply from: rootProject.file('repositories.gradle') 21 | apply from: 'dependencies.gradle' 22 | 23 | configurations { 24 | commonJava { 25 | canBeResolved = false 26 | canBeConsumed = true 27 | } 28 | commonResources { 29 | canBeResolved = false 30 | canBeConsumed = true 31 | } 32 | commonGeneratedResources { 33 | canBeResolved = false 34 | canBeConsumed = true 35 | } 36 | } 37 | 38 | sourceSets { 39 | generated { 40 | resources { 41 | srcDir 'src/generated/resources' 42 | } 43 | } 44 | } 45 | 46 | artifacts { 47 | commonJava sourceSets.main.java.sourceDirectories.singleFile 48 | commonResources sourceSets.main.resources.sourceDirectories.singleFile 49 | commonGeneratedResources sourceSets.generated.resources.sourceDirectories.singleFile 50 | } 51 | 52 | // Implement mcgradleconventions loader attribute 53 | def loaderAttribute = Attribute.of('io.github.mcgradleconventions.loader', String) 54 | ['apiElements', 'runtimeElements', 'sourcesElements', 'javadocElements'].each { variant -> 55 | configurations.named("$variant") { 56 | attributes { 57 | attribute(loaderAttribute, 'common') 58 | } 59 | } 60 | } 61 | sourceSets.configureEach { 62 | [it.compileClasspathConfigurationName, it.runtimeClasspathConfigurationName].each { variant-> 63 | configurations.named("$variant") { 64 | attributes { 65 | attribute(loaderAttribute, 'common') 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /common/src/main/resources/assets/forgivingvoid/lang/tr_tr.json: -------------------------------------------------------------------------------- 1 | { 2 | "death.fell.forgivingvoid": "%1$s dünyanın tepesinden düştü", 3 | "forgivingvoid.configuration.title": "Affedici Boşluk", 4 | "forgivingvoid.configuration.triggerAtDistanceBelow": "Aşağıdaki Mesafede Tetikle", 5 | "forgivingvoid.configuration.triggerAtDistanceBelow.tooltip": "Affedici Boşluğun oyuncuyu affedip gökyüzüne göndermesi gereken asgari y seviyesine olan mesafe.", 6 | "forgivingvoid.configuration.damageOnFall": "Düşmede Hasar", 7 | "forgivingvoid.configuration.damageOnFall.tooltip": "Oyuncu yere indiğinde uygulanan hasar miktarı.", 8 | "forgivingvoid.configuration.fallingHeight": "Düşme Yüksekliği", 9 | "forgivingvoid.configuration.fallingHeight.tooltip": "Boşluktan düştükten sonra oyuncunun düşeceği yükseklik.", 10 | "forgivingvoid.configuration.preventDeath": "Ölümü Önle", 11 | "forgivingvoid.configuration.preventDeath.tooltip": "Boşluğa düşmede ölümü önle (hasarı en az 0.5 kalp kalacak şekilde sınırlar)", 12 | "forgivingvoid.configuration.triggerInOverworld": "Ana Dünyada Tetikle", 13 | "forgivingvoid.configuration.triggerInOverworld.tooltip": "Affedici Boşluğun ana dünya boşluğunda (boyut 0) tetiklenmemesi için false (yanlış) olarak ayarlayın.", 14 | "forgivingvoid.configuration.triggerInNether": "Nether'da Tetikle", 15 | "forgivingvoid.configuration.triggerInNether.tooltip": "Affedici Boşluğun nether boşluğunda (boyut -1) tetiklenmemesi için false (yanlış) olarak ayarlayın.", 16 | "forgivingvoid.configuration.triggerInEnd": "End'de Tetikle", 17 | "forgivingvoid.configuration.triggerInEnd.tooltip": "Affedici Boşluğun end boşluğunda (boyut 1) tetiklenmemesi için false (yanlış) olarak ayarlayın.", 18 | "forgivingvoid.configuration.disableVanillaAntiCheatWhileFalling": "Düşerken Vanilla Hile Korumasını Devre Dışı Bırak", 19 | "forgivingvoid.configuration.disableVanillaAntiCheatWhileFalling.tooltip": "Oyuncular boşluktan düşerken lastik bant gibi davranıyorsa true (doğru) olarak ayarlayın. Herkese açık bir sunucu barındırıyorsanız, bunu yalnızca uygun hile önleme yüklüyse yapmalısınız.", 20 | "forgivingvoid.configuration.dimensionAllowList": "Boyut İzin Listesi", 21 | "forgivingvoid.configuration.dimensionAllowList.tooltip": "Affedici Boşluk için izin verilecek boyut kimliklerinin listesi. triggerInOverworld (Ana Dünyada Tetikle) vb. seçenekler önceliklidir.", 22 | "forgivingvoid.configuration.dimensionDenyList": "Boyut Engel Listesi", 23 | "forgivingvoid.configuration.dimensionDenyList.tooltip": "Affedici Boşluktan engellenecek ek boyut kimliklerinin listesi. triggerInOverworld (Ana Dünyada Tetikle) vb. seçenekler önceliklidir. dimensionAllowList (Boyut İzin Listesi) ayarlanmışsa yoksayılır." 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/publish-snapshot.yml: -------------------------------------------------------------------------------- 1 | name: publish-snapshot 2 | on: 3 | push: 4 | branches: 5 | - '[0-9]+.[0-9]+.[0-9]+' 6 | - '[0-9]+.[0-9]+' 7 | 8 | jobs: 9 | prepare-matrix: 10 | runs-on: ubuntu-latest 11 | outputs: 12 | matrix: ${{ steps.set-matrix.outputs.result }} 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@v4 16 | - name: Preparing matrix 17 | id: set-matrix 18 | uses: actions/github-script@v7 19 | with: 20 | script: | 21 | const fs = require('fs'); 22 | const settingsGradle = fs.readFileSync('settings.gradle', 'utf8'); 23 | const includePattern = /^(?!\s*\/\/)\s*include\s*\(\s*(['"]([^'"]+)['"](?:,\s*['"]([^'"]+)['"])*\s*)\)/gm; 24 | const includes = [...settingsGradle.matchAll(includePattern)] 25 | .flatMap(match => match[0].match(/['"]([^'"]+)['"]/g).map(item => item.replace(/['"]/g, ''))); 26 | const includeFabric = includes.includes('fabric'); 27 | const includeForge = includes.includes('forge'); 28 | const includeNeoForge = includes.includes('neoforge'); 29 | const gradleProperties = fs.readFileSync('gradle.properties', 'utf8'); 30 | const mavenSnapshots = gradleProperties.match(/^(?!#)maven_snapshots\s*=\s*(.+)/m); 31 | return { 32 | loader: ['common', includeFabric ? 'fabric' : false, includeForge ? 'forge' : false, includeNeoForge ? 'neoforge' : false].filter(Boolean), 33 | task: [mavenSnapshots ? 'publish' : 'build'] 34 | }; 35 | publish-snapshot: 36 | runs-on: ubuntu-latest 37 | strategy: 38 | matrix: ${{fromJson(needs.prepare-matrix.outputs.matrix)}} 39 | fail-fast: false 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v4 43 | - name: Validate gradle wrapper 44 | uses: gradle/actions/wrapper-validation@v5 45 | - name: Setup JDK 46 | uses: actions/setup-java@v4 47 | with: 48 | java-version: 21 49 | distribution: temurin 50 | - name: Make gradle wrapper executable 51 | run: chmod +x ./gradlew 52 | - name: Extracting version from properties 53 | shell: bash 54 | run: echo "version=$(cat gradle.properties | grep -w "\bversion\s*=" | cut -d= -f2)" >> $GITHUB_OUTPUT 55 | id: extract-version 56 | - name: Bumping version 57 | uses: TwelveIterationMods/bump-version@v1 58 | with: 59 | version: ${{ steps.extract-version.outputs.version }} 60 | bump: patch 61 | id: bump-version 62 | - name: Publish 63 | run: ./gradlew :${{ matrix.loader }}:${{ matrix.task }} '-Pversion=${{ steps.bump-version.outputs.version }}-SNAPSHOT' '-PmavenUsername=${{ secrets.MAVEN_USER }}' '-PmavenPassword=${{ secrets.MAVEN_PASSWORD }}' 64 | needs: prepare-matrix 65 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /repositories.gradle: -------------------------------------------------------------------------------- 1 | repositories { 2 | maven { 3 | name = 'Twelve Iterations' 4 | url = 'https://maven.twelveiterations.com/repository/maven-public/' 5 | content { 6 | includeGroup 'net.blay09.mods' 7 | } 8 | } 9 | 10 | maven { 11 | name = 'Twelve Iterations Proxy' 12 | url = 'https://maven.twelveiterations.com/repository/maven-proxy/' 13 | content { 14 | includeGroup 'dev.emi' 15 | } 16 | } 17 | 18 | maven { 19 | name = 'CurseMaven' 20 | url = 'https://www.cursemaven.com' 21 | content { 22 | includeGroup 'curse.maven' 23 | } 24 | } 25 | 26 | maven { 27 | name = 'Shedaniel' 28 | url = 'https://maven.shedaniel.me/' 29 | content { 30 | includeGroup "me.shedaniel" 31 | includeGroup "me.shedaniel.cloth" 32 | includeGroup "dev.architectury" 33 | } 34 | } 35 | 36 | maven { 37 | name = 'BlameJared' 38 | url = 'https://maven.blamejared.com' 39 | content { 40 | includeGroup "mezz.jei" 41 | includeGroup "info.journeymap" 42 | includeGroup "mysticdrew" 43 | } 44 | } 45 | 46 | maven { 47 | name = 'Bai' 48 | url = 'https://maven.bai.lol' 49 | content { 50 | includeGroup "lol.bai" 51 | includeGroup "mcp.mobius.waila" 52 | } 53 | } 54 | 55 | maven { 56 | name = 'JitPack' 57 | url = 'https://jitpack.io' 58 | content { 59 | includeGroup "com.github.BlueMap-Minecraft" 60 | includeGroup "com.github.mattidragon" 61 | } 62 | } 63 | 64 | maven { 65 | name = 'MikePrimm' 66 | url = 'https://repo.mikeprimm.com/' 67 | content { 68 | includeGroup "us.dynmap" 69 | } 70 | } 71 | 72 | maven { 73 | name = 'LadySnake' 74 | url = 'https://maven.ladysnake.org/releases' 75 | content { 76 | includeGroup "dev.onyxstudios.cardinal-components-api" 77 | includeGroup "org.ladysnake.cardinal-components-api" 78 | } 79 | } 80 | 81 | maven { 82 | name = 'Siphalor' 83 | url = 'https://maven.siphalor.de/' 84 | content { 85 | includeGroup "de.siphalor" 86 | } 87 | } 88 | 89 | maven { 90 | name = 'Theillusivec4' 91 | url = 'https://maven.theillusivec4.top/' 92 | content { 93 | includeGroup "top.theillusivec4.curios" 94 | } 95 | } 96 | 97 | maven { 98 | name = 'CloudSmith' 99 | url = 'https://dl.cloudsmith.io/public/novamachina-mods/release/maven/' 100 | content { 101 | includeGroup "novamachina.novacore" 102 | includeGroup "novamachina.exnihilosequentia" 103 | } 104 | } 105 | 106 | exclusiveContent { 107 | forRepository { 108 | maven { 109 | name = 'Minecraft' 110 | url = 'https://libraries.minecraft.net/' 111 | } 112 | } 113 | filter { includeGroupAndSubgroups("com.mojang") } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /common/src/main/java/net/blay09/mods/forgivingvoid/ForgivingVoidConfig.java: -------------------------------------------------------------------------------- 1 | package net.blay09.mods.forgivingvoid; 2 | 3 | import net.blay09.mods.balm.Balm; 4 | import net.blay09.mods.balm.platform.config.reflection.Comment; 5 | import net.blay09.mods.balm.platform.config.reflection.Config; 6 | import net.blay09.mods.balm.platform.config.reflection.NestedType; 7 | import net.minecraft.resources.Identifier; 8 | 9 | import java.util.HashSet; 10 | import java.util.List; 11 | import java.util.Set; 12 | 13 | @Config(ForgivingVoid.MOD_ID) 14 | public class ForgivingVoidConfig { 15 | 16 | @Comment("The distance to the minimum y level at which Forgiving Void should forgive the player and send them towards the sky.") 17 | public int triggerAtDistanceBelow = 32; 18 | 19 | @Comment("Set to ABSOLUTE for absolute fall damage in half hearts, RELATIVE_CURRENT for a percentage of their current health, or RELATIVE_MAX for a percentage of their max health.") 20 | public DamageOnFallMode damageOnFallMode = DamageOnFallMode.ABSOLUTE; 21 | 22 | @Comment("The amount of damage applied to the player when they land.") 23 | public float damageOnFall = 19; 24 | 25 | @Comment("The height from which the player will be falling after falling through the void.") 26 | public int fallingHeight = 300; 27 | 28 | @Comment("Set to true to make Forgiving Void return the player to the sky above their last grounded position instead of their current position. Prevents players from using Forgiving Void to fly across larger distances.") 29 | public boolean returnToLastGrounded = true; 30 | 31 | @Comment("Prevent death on void fall (limits damage to leave at least 0.5 hearts)") 32 | public boolean preventDeath = false; 33 | 34 | @Comment("Set to false to make Forgiving Void not trigger in the overworld void (dimension 0).") 35 | public boolean triggerInOverworld = true; 36 | 37 | @Comment("Set to false to make Forgiving Void not trigger in the nether void (dimension -1).") 38 | public boolean triggerInNether = true; 39 | 40 | @Comment("Set to false to make Forgiving Void not trigger in the end void (dimension 1).") 41 | public boolean triggerInEnd = true; 42 | 43 | @Comment("Set to true if players are rubber-banding while falling through the void. If you're hosting a public server, you should only do this if you have proper anti-cheat installed.") 44 | public boolean disableVanillaAntiCheatWhileFalling = true; 45 | 46 | @Comment("Set to true to have tridents with loyalty be affected by Forgiving Void. Not supported on Forge.") 47 | public boolean tridentForgiveness = false; 48 | 49 | @NestedType(String.class) 50 | @Comment("Effects applied to a player when they fall through the void, in the format \"effect|duration|amplifier\"") 51 | public List fallThroughVoidEffects = List.of("minecraft:blindness|60|3"); 52 | 53 | @Comment("List of dimension ids to be allowed for Forgiving Void. Options triggerInOverworld etc. take priority.") 54 | @NestedType(Identifier.class) 55 | public Set dimensionAllowList = new HashSet<>(); 56 | 57 | @Comment("List of additional dimension ids to be deny-listed from Forgiving Void. Options triggerInOverworld etc. take priority. Ignored if dimensionAllowList is set.") 58 | @NestedType(Identifier.class) 59 | public Set dimensionDenyList = new HashSet<>(); 60 | 61 | @Comment("List of entity ids to be allowed for Forgiving Void. On Forge this only supports living entities.") 62 | @NestedType(Identifier.class) 63 | public Set entityAllowList = Set.of(Identifier.withDefaultNamespace("player")); 64 | 65 | public static ForgivingVoidConfig getActive() { 66 | return Balm.config().getActiveConfig(ForgivingVoidConfig.class); 67 | } 68 | 69 | public static void initialize() { 70 | Balm.config().registerConfig(ForgivingVoidConfig.class); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /common/src/main/resources/assets/forgivingvoid/lang/en_us.json: -------------------------------------------------------------------------------- 1 | { 2 | "death.fell.forgivingvoid": "%1$s fell from the top of the world", 3 | "forgivingvoid.configuration.title": "Forgiving Void", 4 | "forgivingvoid.configuration.triggerAtDistanceBelow": "Trigger at Distance Below", 5 | "forgivingvoid.configuration.triggerAtDistanceBelow.tooltip": "The distance to the minimum y level at which Forgiving Void should forgive the player and send them towards the sky.", 6 | "forgivingvoid.configuration.damageOnFall": "Damage on Fall", 7 | "forgivingvoid.configuration.damageOnFall.tooltip": "The amount of damage applied to the player when they land.", 8 | "forgivingvoid.configuration.damageOnFallMode": "Damage on Fall Mode", 9 | "forgivingvoid.configuration.damageOnFallMode.tooltip": "Set to ABSOLUTE for absolute fall damage in half hearts, RELATIVE_CURRENT for a percentage of their current health, or RELATIVE_MAX for a percentage of their max health.", 10 | "forgivingvoid.configuration.fallingHeight": "Falling Height", 11 | "forgivingvoid.configuration.fallingHeight.tooltip": "The height from which the player will be falling after falling through the void.", 12 | "forgivingvoid.configuration.preventDeath": "Prevent Death", 13 | "forgivingvoid.configuration.preventDeath.tooltip": "Prevent death on void fall (limits damage to leave at least 0.5 hearts)", 14 | "forgivingvoid.configuration.triggerInOverworld": "Trigger in Overworld", 15 | "forgivingvoid.configuration.triggerInOverworld.tooltip": "Set to false to make Forgiving Void not trigger in the overworld void (dimension 0).", 16 | "forgivingvoid.configuration.triggerInNether": "Trigger in Nether", 17 | "forgivingvoid.configuration.triggerInNether.tooltip": "Set to false to make Forgiving Void not trigger in the nether void (dimension -1).", 18 | "forgivingvoid.configuration.triggerInEnd": "Trigger in End", 19 | "forgivingvoid.configuration.triggerInEnd.tooltip": "Set to false to make Forgiving Void not trigger in the end void (dimension 1).", 20 | "forgivingvoid.configuration.disableVanillaAntiCheatWhileFalling": "Disable Vanilla AntiCheat While Falling", 21 | "forgivingvoid.configuration.disableVanillaAntiCheatWhileFalling.tooltip": "Set to true if players are rubber-banding while falling through the void. If you're hosting a public server, you should only do this if you have proper anti-cheat installed.", 22 | "forgivingvoid.configuration.dimensionAllowList": "Dimension Allow List", 23 | "forgivingvoid.configuration.dimensionAllowList.tooltip": "List of dimension ids to be allowed for Forgiving Void. Options triggerInOverworld etc. take priority.", 24 | "forgivingvoid.configuration.dimensionDenyList": "Dimension Deny List", 25 | "forgivingvoid.configuration.dimensionDenyList.tooltip": "List of additional dimension ids to be deny-listed from Forgiving Void. Options triggerInOverworld etc. take priority. Ignored if dimensionAllowList is set.", 26 | "forgivingvoid.configuration.returnToLastGrounded": "Return to Last Grounded", 27 | "forgivingvoid.configuration.returnToLastGrounded.tooltip": "Set to true to make Forgiving Void return the player to the sky above their last grounded position instead of their current position. Prevents players from using Forgiving Void to fly across larger distances.", 28 | "forgivingvoid.configuration.tridentForgiveness":"Trident Forgiveness", 29 | "forgivingvoid.configuration.tridentForgiveness.tooltip":"Set to true to have tridents with loyalty be affected by Forgiving Void. Not supported on Forge.", 30 | "forgivingvoid.configuration.entityAllowList": "Entity Allow List", 31 | "forgivingvoid.configuration.entityAllowList.tooltip": "List of entity ids to be allowed for Forgiving Void. On Forge this only supports living entities.", 32 | "forgivingvoid.configuration.fallThroughVoidEffects": "Fall Through Void Effects", 33 | "forgivingvoid.configuration.fallThroughVoidEffects.tooltip": "Effects applied to a player when they fall through the void, in the format \"effect|duration|amplifier\"" 34 | } -------------------------------------------------------------------------------- /neoforge/build.gradle: -------------------------------------------------------------------------------- 1 | import net.darkhax.curseforgegradle.TaskPublishCurseForge 2 | 3 | plugins { 4 | id 'multiloader-loader' 5 | alias libs.plugins.modDevGradle 6 | alias libs.plugins.curseForgeGradle 7 | alias libs.plugins.modrinthMinotaur 8 | } 9 | 10 | neoForge { 11 | version = libs.neoForge.get().version 12 | // Automatically enable neoforge AccessTransformers if the file exists 13 | def at = project(':common').file('src/main/resources/META-INF/accesstransformer.cfg') 14 | if (at.exists()) { 15 | accessTransformers.from(at.absolutePath) 16 | } 17 | 18 | parchment.parchmentArtifact = libs.parchment.get().toString() 19 | 20 | runs { 21 | configureEach { 22 | systemProperty('neoforge.enabledGameTestNamespaces', mod_id) 23 | ideName = "NeoForge ${it.name.capitalize()} (${project.path})" 24 | } 25 | 26 | client { 27 | client() 28 | } 29 | 30 | server { 31 | server() 32 | } 33 | } 34 | 35 | mods { 36 | "${mod_id}" { 37 | sourceSet sourceSets.main 38 | } 39 | } 40 | } 41 | 42 | sourceSets.main.resources { srcDir 'src/generated/resources' } 43 | 44 | apply from: rootProject.file('repositories.gradle') 45 | apply from: 'dependencies.gradle' 46 | 47 | tasks.register('curseforge', TaskPublishCurseForge) { 48 | dependsOn('build') 49 | description = 'Publishes the NeoForge build to CurseForge.' 50 | group = 'publishing' 51 | 52 | apiToken = project.findProperty("curseforge.api_key") ?: System.getenv("CURSEFORGE_TOKEN") ?: "none" 53 | 54 | def projectId = findProperty("curseforge_project_id") 55 | onlyIf { 56 | projectId != null 57 | } 58 | if (projectId) { 59 | def mainFile = upload(findProperty("curseforge_project_id"), jar.archiveFile.get().asFile) 60 | mainFile.changelog = rootProject.file('CHANGELOG.md').text 61 | mainFile.addRequirement("balm") 62 | mainFile.addGameVersion(libs.minecraft.get().version) 63 | mainFile.releaseType = "release" 64 | project.findProperty("curseforge_environments")?.split(",")?.toList()?.each { mainFile.addEnvironment(it) } 65 | mainFile.addModLoader("NeoForge") 66 | } 67 | } 68 | 69 | modrinth { 70 | token = project.findProperty("modrinth.token") ?: System.getenv("MODRINTH_TOKEN") ?: "none" 71 | projectId = findProperty("modrinth_project_id") 72 | versionType = "release" 73 | versionNumber = project.version + "+neoforge-" + libs.minecraft.get().version 74 | uploadFile = jar 75 | changelog = rootProject.file("CHANGELOG.md").text 76 | gameVersions = [libs.minecraft.get().version] 77 | syncBodyFrom = rootProject.file("modpage.md").text 78 | loaders = ['neoforge'] 79 | dependencies { 80 | required.project "balm" 81 | } 82 | } 83 | 84 | def neoForgeSnapshotUrl = findProperty("neoforge_snapshot_url") 85 | if (neoForgeSnapshotUrl != null && !neoForgeSnapshotUrl.isBlank()) { 86 | repositories { 87 | maven { 88 | name = 'Maven for NeoForge Snapshots' 89 | url = neoForgeSnapshotUrl 90 | content { 91 | includeModule('net.neoforged', 'neoforge') 92 | includeModule('net.neoforged', 'testframework') 93 | } 94 | } 95 | } 96 | } 97 | 98 | // Implement mcgradleconventions loader attribute 99 | def loaderAttribute = Attribute.of('io.github.mcgradleconventions.loader', String) 100 | ['apiElements', 'runtimeElements', 'sourcesElements', 'javadocElements'].each { variant -> 101 | configurations.named("$variant") { 102 | attributes { 103 | attribute(loaderAttribute, 'neoforge') 104 | } 105 | } 106 | } 107 | sourceSets.configureEach { 108 | [it.compileClasspathConfigurationName, it.runtimeClasspathConfigurationName, it.getTaskName(null, 'jarJar')].each { variant-> 109 | configurations.named("$variant") { 110 | attributes { 111 | attribute(loaderAttribute, 'neoforge') 112 | } 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /fabric/build.gradle: -------------------------------------------------------------------------------- 1 | import net.darkhax.curseforgegradle.TaskPublishCurseForge 2 | 3 | plugins { 4 | id 'multiloader-loader' 5 | alias libs.plugins.fabricLoom 6 | alias libs.plugins.curseForgeGradle 7 | alias libs.plugins.modrinthMinotaur 8 | } 9 | 10 | dependencies { 11 | minecraft libs.minecraft 12 | mappings loom.layered() { 13 | officialMojangMappings() 14 | parchment variantOf(libs.parchment) { 15 | artifactType('zip') 16 | } 17 | } 18 | modImplementation libs.fabricLoader 19 | modImplementation libs.fabricApi 20 | } 21 | 22 | apply from: rootProject.file('repositories.gradle') 23 | apply from: 'dependencies.gradle' 24 | 25 | loom { 26 | def aw = project(":common").file("src/main/resources/${mod_id}.accesswidener") 27 | if (aw.exists()) { 28 | accessWidenerPath.set(aw) 29 | } 30 | 31 | mixin { 32 | defaultRefmapName.set("${mod_id}.refmap.json") 33 | } 34 | 35 | runs { 36 | client { 37 | client() 38 | setConfigName("fabric Client") 39 | ideConfigGenerated(true) 40 | runDir("runs/client") 41 | } 42 | server { 43 | server() 44 | setConfigName("fabric Server") 45 | ideConfigGenerated(true) 46 | runDir("runs/server") 47 | } 48 | data { 49 | inherit client 50 | setConfigName("fabric Data") 51 | ideConfigGenerated(true) 52 | runDir("build/datagen") 53 | 54 | vmArg "-Dfabric-api.datagen" 55 | vmArg "-Dfabric-api.datagen.output-dir=${project(":common").file("src/generated/resources")}" 56 | vmArg "-Dfabric-api.datagen.modid=${mod_id}" 57 | } 58 | } 59 | } 60 | 61 | // Implement mcgradleconventions loader attribute 62 | def loaderAttribute = Attribute.of('io.github.mcgradleconventions.loader', String) 63 | ['apiElements', 'runtimeElements', 'sourcesElements', 'javadocElements', 'includeInternal', 'modCompileClasspath'].each { variant -> 64 | configurations.named("$variant") { 65 | attributes { 66 | attribute(loaderAttribute, 'fabric') 67 | } 68 | } 69 | } 70 | sourceSets.configureEach { 71 | [it.compileClasspathConfigurationName, it.runtimeClasspathConfigurationName].each { variant-> 72 | configurations.named("$variant") { 73 | attributes { 74 | attribute(loaderAttribute, 'fabric') 75 | } 76 | } 77 | } 78 | } 79 | loom.remapConfigurations.configureEach { 80 | configurations.named(it.name) { 81 | attributes { 82 | attribute(loaderAttribute, 'fabric') 83 | } 84 | } 85 | } 86 | 87 | tasks.register('curseforge', TaskPublishCurseForge) { 88 | dependsOn('build') 89 | description = 'Publishes the Fabric build to CurseForge.' 90 | group = 'publishing' 91 | 92 | apiToken = project.findProperty("curseforge.api_key") ?: System.getenv("CURSEFORGE_TOKEN") ?: "none" 93 | 94 | def projectId = findProperty("curseforge_project_id") 95 | onlyIf { 96 | projectId != null 97 | } 98 | if (projectId) { 99 | def mainFile = upload(findProperty("curseforge_project_id"), remapJar.archiveFile.get().asFile) 100 | mainFile.changelog = rootProject.file('CHANGELOG.md').text 101 | mainFile.addRequirement("fabric-api") 102 | mainFile.addRequirement("balm") 103 | mainFile.addGameVersion(libs.minecraft.get().version) 104 | mainFile.releaseType = "release" 105 | project.findProperty("curseforge_environments")?.split(",")?.toList()?.each { mainFile.addEnvironment(it) } 106 | } 107 | } 108 | 109 | modrinth { 110 | token = project.findProperty("modrinth.token") ?: System.getenv("MODRINTH_TOKEN") ?: "none" 111 | projectId = findProperty("modrinth_project_id") 112 | versionType = "release" 113 | versionNumber = project.version + "+fabric-" + libs.minecraft.get().version 114 | uploadFile = remapJar 115 | changelog = rootProject.file("CHANGELOG.md").text 116 | gameVersions = [libs.minecraft.get().version] 117 | syncBodyFrom = rootProject.file("modpage.md").text 118 | loaders = ['fabric'] 119 | dependencies { 120 | required.project "fabric-api" 121 | required.project "balm" 122 | } 123 | } -------------------------------------------------------------------------------- /forge/build.gradle: -------------------------------------------------------------------------------- 1 | import net.darkhax.curseforgegradle.TaskPublishCurseForge 2 | 3 | plugins { 4 | id 'multiloader-loader' 5 | alias libs.plugins.forgeGradle 6 | alias libs.plugins.mixin 7 | alias libs.plugins.curseForgeGradle 8 | alias libs.plugins.modrinthMinotaur 9 | } 10 | 11 | mixin { 12 | config("${mod_id}.mixins.json") 13 | config("${mod_id}.forge.mixins.json") 14 | } 15 | 16 | jar { 17 | manifest { 18 | attributes["MixinConfigs"] = "${mod_id}.mixins.json,${mod_id}.forge.mixins.json" 19 | } 20 | } 21 | 22 | minecraft { 23 | mappings channel: 'official', version: libs.minecraft.get().version 24 | 25 | copyIdeResources = true //Calls processResources when in dev 26 | 27 | reobf = false // Forge 1.20.6+ uses official mappings at runtime, so we shouldn't reobf from official to SRG 28 | 29 | // Automatically enable forge AccessTransformers if the file exists 30 | def at = file('src/main/resources/META-INF/accesstransformer.cfg') 31 | if (at.exists()) { 32 | accessTransformer = at 33 | } 34 | 35 | runs { 36 | client { 37 | workingDirectory file('runs/client') 38 | ideaModule "${rootProject.name}.${project.name}.main" 39 | taskName "Client" 40 | 41 | property 'forge.enabledGameTestNamespaces', mod_id 42 | 43 | mods { 44 | modClientRun { 45 | source sourceSets.main 46 | } 47 | } 48 | } 49 | 50 | server { 51 | workingDirectory file('runs/server') 52 | ideaModule "${rootProject.name}.${project.name}.main" 53 | taskName "Server" 54 | 55 | property 'forge.enabledGameTestNamespaces', mod_id 56 | 57 | mods { 58 | modServerRun { 59 | source sourceSets.main 60 | } 61 | } 62 | } 63 | 64 | data { 65 | workingDirectory file('runs/data') 66 | ideaModule "${rootProject.name}.${project.name}.main" 67 | args '--mod', mod_id, '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources/') 68 | taskName "Data" 69 | 70 | mods { 71 | modDataRun { 72 | source sourceSets.main 73 | } 74 | } 75 | } 76 | } 77 | } 78 | 79 | sourceSets.main.resources.srcDir 'src/generated/resources' 80 | 81 | dependencies { 82 | def mcVersion = libs.minecraft.get().version 83 | def forgeVersion = libs.forge.get().version 84 | minecraft "net.minecraftforge:forge:${mcVersion}-${forgeVersion}" 85 | def mixinVersion = libs.mixin.get().version 86 | annotationProcessor "org.spongepowered:mixin:${mixinVersion}:processor" 87 | } 88 | 89 | apply from: rootProject.file('repositories.gradle') 90 | apply from: 'dependencies.gradle' 91 | 92 | publishing { 93 | publications { 94 | mavenJava(MavenPublication) { 95 | fg.component(it) 96 | } 97 | } 98 | } 99 | 100 | tasks.register('curseforge', TaskPublishCurseForge) { 101 | dependsOn('build') 102 | description = 'Publishes the Forge build to CurseForge.' 103 | group = 'publishing' 104 | 105 | apiToken = project.findProperty("curseforge.api_key") ?: System.getenv("CURSEFORGE_TOKEN") ?: "none" 106 | 107 | def projectId = findProperty("curseforge_project_id") 108 | onlyIf { 109 | projectId != null 110 | } 111 | if (projectId) { 112 | def mainFile = upload(findProperty("curseforge_project_id"), file("${project.buildDir}/libs/${base.archivesName}-${version}.jar")) 113 | mainFile.changelog = rootProject.file('CHANGELOG.md').text 114 | mainFile.addRequirement("balm") 115 | mainFile.addGameVersion(libs.minecraft.get().version) 116 | mainFile.releaseType = "release" 117 | project.findProperty("curseforge_environments")?.split(",")?.toList()?.each { mainFile.addEnvironment(it) } 118 | } 119 | } 120 | 121 | modrinth { 122 | token = project.findProperty("modrinth.token") ?: System.getenv("MODRINTH_TOKEN") ?: "none" 123 | projectId = findProperty("modrinth_project_id") 124 | versionType = "release" 125 | versionNumber = project.version + "+forge-" + libs.minecraft.get().version 126 | uploadFile = jar 127 | changelog = rootProject.file("CHANGELOG.md").text 128 | gameVersions = [libs.minecraft.get().version] 129 | syncBodyFrom = rootProject.file("modpage.md").text 130 | loaders = ['forge'] 131 | dependencies { 132 | required.project "balm" 133 | } 134 | } 135 | 136 | sourceSets.each { 137 | def dir = layout.buildDirectory.dir("sourcesSets/$it.name") 138 | it.output.resourcesDir = dir 139 | it.java.destinationDirectory = dir 140 | } 141 | 142 | // Implement mcgradleconventions loader attribute 143 | def loaderAttribute = Attribute.of('io.github.mcgradleconventions.loader', String) 144 | ['apiElements', 'runtimeElements', 'sourcesElements', 'javadocElements'].each { variant -> 145 | configurations.named("$variant") { 146 | attributes { 147 | attribute(loaderAttribute, 'forge') 148 | } 149 | } 150 | } 151 | sourceSets.configureEach { 152 | [it.compileClasspathConfigurationName, it.runtimeClasspathConfigurationName].each { variant-> 153 | configurations.named("$variant") { 154 | attributes { 155 | attribute(loaderAttribute, 'forge') 156 | } 157 | } 158 | } 159 | } -------------------------------------------------------------------------------- /.github/workflows/publish-release.yml: -------------------------------------------------------------------------------- 1 | name: publish-release 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | forge: 6 | description: 'Forge' 7 | required: true 8 | type: boolean 9 | default: true 10 | fabric: 11 | description: 'Fabric' 12 | required: true 13 | type: boolean 14 | default: true 15 | neoforge: 16 | description: 'NeoForge' 17 | required: true 18 | type: boolean 19 | default: true 20 | maven: 21 | description: 'Maven' 22 | required: true 23 | type: boolean 24 | default: true 25 | modrinth: 26 | description: 'Modrinth' 27 | required: true 28 | type: boolean 29 | default: true 30 | curseforge: 31 | description: 'CurseForge' 32 | required: true 33 | type: boolean 34 | default: true 35 | 36 | jobs: 37 | create-release: 38 | runs-on: ubuntu-latest 39 | outputs: 40 | ref: v${{ steps.bump-version.outputs.version }} 41 | version: ${{ steps.bump-version.outputs.version }} 42 | build-matrix: ${{ steps.set-build-matrix.outputs.result }} 43 | publish-matrix: ${{ steps.set-publish-matrix.outputs.result }} 44 | steps: 45 | - name: Checkout repository 46 | uses: actions/checkout@v4 47 | - name: Extracting version from properties 48 | shell: bash 49 | run: echo "version=$(cat gradle.properties | grep -w "\bversion\s*=" | cut -d= -f2)" >> $GITHUB_OUTPUT 50 | id: extract-version 51 | - name: Bumping version 52 | uses: TwelveIterationMods/bump-version@v1 53 | with: 54 | version: ${{ steps.extract-version.outputs.version }} 55 | bump: patch 56 | id: bump-version 57 | - name: Updating version properties 58 | run: | 59 | sed -i "s/^\s*version\s*=.*/version = ${{ steps.bump-version.outputs.version }}/g" gradle.properties 60 | git config user.name "GitHub Actions" 61 | git config user.email "<>" 62 | git commit -am "Set version to ${{ steps.bump-version.outputs.version }}" 63 | git push origin ${BRANCH_NAME} 64 | git tag -a "v${{ steps.bump-version.outputs.version }}" -m "Release ${{ steps.bump-version.outputs.version }}" 65 | git push origin "v${{ steps.bump-version.outputs.version }}" 66 | shell: bash 67 | env: 68 | BRANCH_NAME: ${{ github.head_ref || github.ref_name }} 69 | - name: Preparing build matrix 70 | id: set-build-matrix 71 | uses: actions/github-script@v7 72 | with: 73 | script: | 74 | const fs = require('fs'); 75 | const settingsGradle = fs.readFileSync('settings.gradle', 'utf8'); 76 | const includePattern = /^(?!\s*\/\/)\s*include\s*\(\s*(['"]([^'"]+)['"](?:,\s*['"]([^'"]+)['"])*\s*)\)/gm; 77 | const includes = [...settingsGradle.matchAll(includePattern)].flatMap(match => match[0].match(/['"]([^'"]+)['"]/g).map(item => item.replace(/['"]/g, ''))); 78 | const includeFabric = includes.includes('fabric') && ${{inputs.fabric}}; 79 | const includeForge = includes.includes('forge') && ${{inputs.forge}}; 80 | const includeNeoForge = includes.includes('neoforge') && ${{inputs.neoforge}}; 81 | return { 82 | loader: [includeFabric ? 'fabric' : false, includeForge ? 'forge' : false, includeNeoForge ? 'neoforge' : false].filter(Boolean), 83 | } 84 | - name: Preparing publish matrix 85 | id: set-publish-matrix 86 | uses: actions/github-script@v7 87 | with: 88 | script: | 89 | const fs = require('fs'); 90 | const settingsGradle = fs.readFileSync('settings.gradle', 'utf8'); 91 | const includePattern = /^(?!\s*\/\/)\s*include\s*\(\s*(['"]([^'"]+)['"](?:,\s*['"]([^'"]+)['"])*\s*)\)/gm; 92 | const includes = [...settingsGradle.matchAll(includePattern)].flatMap(match => match[0].match(/['"]([^'"]+)['"]/g).map(item => item.replace(/['"]/g, ''))); 93 | const includeFabric = includes.includes('fabric') && ${{inputs.fabric}}; 94 | const includeForge = includes.includes('forge') && ${{inputs.forge}}; 95 | const includeNeoForge = includes.includes('neoforge') && ${{inputs.neoforge}}; 96 | const gradleProperties = fs.readFileSync('gradle.properties', 'utf8'); 97 | const curseForgeProjectId = gradleProperties.match(/^(?!#)curseforge_project_id\s*=\s*(.+)/m); 98 | const modrinthProjectId = gradleProperties.match(/^(?!#)modrinth_project_id\s*=\s*(.+)/m); 99 | const mavenReleases = gradleProperties.match(/^(?!#)maven_releases\s*=\s*(.+)/m); 100 | const publishCurseForge = curseForgeProjectId && ${{inputs.curseforge}}; 101 | const publishModrinth = modrinthProjectId && ${{inputs.modrinth}}; 102 | const publishMaven = mavenReleases && ${{inputs.maven}}; 103 | return { 104 | loader: ['common', includeFabric ? 'fabric' : false, includeForge ? 'forge' : false, includeNeoForge ? 'neoforge' : false].filter(Boolean), 105 | site: [publishCurseForge ? 'curseforge' : false, publishModrinth ? 'modrinth' : false, publishMaven ? 'publish' : false].filter(Boolean), 106 | exclude: [ 107 | {loader: 'common', site: 'curseforge'}, 108 | {loader: 'common', site: 'modrinth'} 109 | ] 110 | } 111 | build-common: 112 | runs-on: ubuntu-latest 113 | steps: 114 | - name: Checkout repository 115 | uses: actions/checkout@v4 116 | with: 117 | ref: ${{ needs.create-release.outputs.ref }} 118 | - name: Validate gradle wrapper 119 | uses: gradle/actions/wrapper-validation@v5 120 | - name: Setup JDK 121 | uses: actions/setup-java@v4 122 | with: 123 | java-version: 21 124 | distribution: temurin 125 | cache: 'gradle' 126 | - name: Make gradle wrapper executable 127 | run: chmod +x ./gradlew 128 | - name: Build common artifact 129 | run: ./gradlew :common:build '-Pversion=${{needs.create-release.outputs.version}}' 130 | - name: Upload common artifact 131 | uses: actions/upload-artifact@v4 132 | with: 133 | name: common-artifact 134 | path: common/build 135 | needs: create-release 136 | build-release: 137 | runs-on: ubuntu-latest 138 | strategy: 139 | matrix: ${{fromJson(needs.create-release.outputs.build-matrix)}} 140 | fail-fast: false 141 | steps: 142 | - name: Checkout repository 143 | uses: actions/checkout@v4 144 | with: 145 | ref: ${{ needs.create-release.outputs.ref }} 146 | - name: Validate gradle wrapper 147 | uses: gradle/actions/wrapper-validation@v5 148 | - name: Setup JDK 149 | uses: actions/setup-java@v4 150 | with: 151 | java-version: 21 152 | distribution: temurin 153 | cache: 'gradle' 154 | - name: Make gradle wrapper executable 155 | run: chmod +x ./gradlew 156 | - name: Download common artifact 157 | uses: actions/download-artifact@v4 158 | with: 159 | name: common-artifact 160 | path: common/build 161 | - name: Build ${{ matrix.loader }} artifact 162 | run: ./gradlew :${{ matrix.loader }}:build '-Pversion=${{needs.create-release.outputs.version}}' 163 | - name: Upload ${{ matrix.loader }} artifact 164 | uses: actions/upload-artifact@v4 165 | with: 166 | name: ${{ matrix.loader }}-artifact 167 | path: ${{ matrix.loader }}/build 168 | needs: 169 | - create-release 170 | - build-common 171 | publish-release: 172 | runs-on: ubuntu-latest 173 | strategy: 174 | matrix: ${{fromJson(needs.create-release.outputs.publish-matrix)}} 175 | fail-fast: false 176 | steps: 177 | - name: Checkout repository 178 | uses: actions/checkout@v4 179 | with: 180 | ref: ${{ needs.create-release.outputs.ref }} 181 | - name: Download ${{ matrix.loader }} artifact 182 | uses: actions/download-artifact@v4 183 | with: 184 | name: ${{ matrix.loader }}-artifact 185 | path: ${{ matrix.loader }}/build 186 | - name: Validate gradle wrapper 187 | uses: gradle/actions/wrapper-validation@v5 188 | - name: Setup JDK 189 | uses: actions/setup-java@v4 190 | with: 191 | java-version: 21 192 | distribution: temurin 193 | cache: 'gradle' 194 | - name: Make gradle wrapper executable 195 | run: chmod +x ./gradlew 196 | - name: Publish 197 | run: ./gradlew :${{ matrix.loader }}:${{ matrix.site }} '-Pversion=${{needs.create-release.outputs.version}}' '-PmavenUsername=${{ secrets.MAVEN_USER }}' '-PmavenPassword=${{ secrets.MAVEN_PASSWORD }}' 198 | env: 199 | CURSEFORGE_TOKEN: ${{secrets.CURSEFORGE_TOKEN}} 200 | MODRINTH_TOKEN: ${{secrets.MODRINTH_TOKEN}} 201 | needs: 202 | - create-release 203 | - build-common 204 | - build-release -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH="\\\"\\\"" 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | if ! command -v java >/dev/null 2>&1 137 | then 138 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 139 | 140 | Please set the JAVA_HOME variable in your environment to match the 141 | location of your Java installation." 142 | fi 143 | fi 144 | 145 | # Increase the maximum file descriptors if we can. 146 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 147 | case $MAX_FD in #( 148 | max*) 149 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 150 | # shellcheck disable=SC2039,SC3045 151 | MAX_FD=$( ulimit -H -n ) || 152 | warn "Could not query maximum file descriptor limit" 153 | esac 154 | case $MAX_FD in #( 155 | '' | soft) :;; #( 156 | *) 157 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 158 | # shellcheck disable=SC2039,SC3045 159 | ulimit -n "$MAX_FD" || 160 | warn "Could not set maximum file descriptor limit to $MAX_FD" 161 | esac 162 | fi 163 | 164 | # Collect all arguments for the java command, stacking in reverse order: 165 | # * args from the command line 166 | # * the main class name 167 | # * -classpath 168 | # * -D...appname settings 169 | # * --module-path (only if needed) 170 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 171 | 172 | # For Cygwin or MSYS, switch paths to Windows format before running java 173 | if "$cygwin" || "$msys" ; then 174 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 175 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 176 | 177 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 178 | 179 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 180 | for arg do 181 | if 182 | case $arg in #( 183 | -*) false ;; # don't mess with options #( 184 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 185 | [ -e "$t" ] ;; #( 186 | *) false ;; 187 | esac 188 | then 189 | arg=$( cygpath --path --ignore --mixed "$arg" ) 190 | fi 191 | # Roll the args list around exactly as many times as the number of 192 | # args, so each arg winds up back in the position where it started, but 193 | # possibly modified. 194 | # 195 | # NB: a `for` loop captures its iteration list before it begins, so 196 | # changing the positional parameters here affects neither the number of 197 | # iterations, nor the values presented in `arg`. 198 | shift # remove old arg 199 | set -- "$@" "$arg" # push replacement arg 200 | done 201 | fi 202 | 203 | 204 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 205 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 206 | 207 | # Collect all arguments for the java command: 208 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 209 | # and any embedded shellness will be escaped. 210 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 211 | # treated as '${Hostname}' itself on the command line. 212 | 213 | set -- \ 214 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 215 | -classpath "$CLASSPATH" \ 216 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ 217 | "$@" 218 | 219 | # Stop when "xargs" is not available. 220 | if ! command -v xargs >/dev/null 2>&1 221 | then 222 | die "xargs is not available" 223 | fi 224 | 225 | # Use "xargs" to parse quoted args. 226 | # 227 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 228 | # 229 | # In Bash we could simply go: 230 | # 231 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 232 | # set -- "${ARGS[@]}" "$@" 233 | # 234 | # but POSIX shell has neither arrays nor command substitution, so instead we 235 | # post-process each arg (as a line of input to sed) to backslash-escape any 236 | # character that might be a shell metacharacter, then use eval to reverse 237 | # that process (while maintaining the separation between arguments), and wrap 238 | # the whole thing up as a single "set" statement. 239 | # 240 | # This will of course break if any of these variables contains a newline or 241 | # an unmatched quote. 242 | # 243 | 244 | eval "set -- $( 245 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 246 | xargs -n1 | 247 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 248 | tr '\n' ' ' 249 | )" '"$@"' 250 | 251 | exec "$JAVACMD" "$@" 252 | -------------------------------------------------------------------------------- /common/src/main/java/net/blay09/mods/forgivingvoid/ForgivingVoid.java: -------------------------------------------------------------------------------- 1 | package net.blay09.mods.forgivingvoid; 2 | 3 | import net.blay09.mods.balm.Balm; 4 | import net.blay09.mods.balm.core.BalmRegistrars; 5 | import net.blay09.mods.balm.platform.event.callback.LivingEntityCallback; 6 | import net.blay09.mods.balm.platform.event.callback.ServerTickCallback; 7 | import net.blay09.mods.forgivingvoid.mixin.ServerGamePacketListenerImplAccessor; 8 | import net.blay09.mods.forgivingvoid.mixin.ServerPlayerAccessor; 9 | import net.blay09.mods.forgivingvoid.mixin.ThrownTridentAccessor; 10 | import net.minecraft.core.BlockPos; 11 | import net.minecraft.core.registries.BuiltInRegistries; 12 | import net.minecraft.nbt.CompoundTag; 13 | import net.minecraft.resources.ResourceKey; 14 | import net.minecraft.resources.Identifier; 15 | import net.minecraft.server.level.ServerPlayer; 16 | import net.minecraft.world.effect.MobEffectInstance; 17 | import net.minecraft.world.entity.Entity; 18 | import net.minecraft.world.entity.LivingEntity; 19 | import net.minecraft.world.entity.player.Player; 20 | import net.minecraft.world.entity.projectile.arrow.ThrownTrident; 21 | import net.minecraft.world.level.Level; 22 | import net.minecraft.world.level.block.Block; 23 | import net.minecraft.world.level.block.Blocks; 24 | import org.jetbrains.annotations.Nullable; 25 | import org.slf4j.Logger; 26 | import org.slf4j.LoggerFactory; 27 | 28 | import java.util.ArrayList; 29 | import java.util.Set; 30 | 31 | public class ForgivingVoid { 32 | 33 | public static final String MOD_ID = "forgivingvoid"; 34 | 35 | public static final Logger logger = LoggerFactory.getLogger(ForgivingVoid.class); 36 | 37 | public static void initialize(BalmRegistrars registrars) { 38 | ForgivingVoidConfig.initialize(); 39 | 40 | ServerTickCallback.ServerEntityTick.BEFORE.register(ForgivingVoid::onEntityTick); 41 | LivingEntityCallback.Fall.Before.EVENT.register(ForgivingVoid::onLivingEntityFall); 42 | } 43 | 44 | public static void onEntityTick(Entity entity) { 45 | if (!isAllowedEntity(entity)) { 46 | return; 47 | } 48 | 49 | int triggerAtY = entity.level().getMinY() - ForgivingVoidConfig.getActive().triggerAtDistanceBelow; 50 | boolean isInVoid = entity.getY() < triggerAtY && entity.yo < triggerAtY; 51 | boolean isTeleporting = entity instanceof ServerPlayer player && ((ServerGamePacketListenerImplAccessor) player.connection).getAwaitingPositionFromClient() != null; 52 | CompoundTag persistentData = Balm.hooks().getPersistentData(entity); 53 | if (entity.onGround()) { 54 | persistentData.putLong("LastGroundedPos", entity.blockPosition().asLong()); 55 | } 56 | 57 | if (isInVoid && !isTeleporting && isEnabledForDimension(entity.level().dimension()) && fireForgivingVoidEvent(entity)) { 58 | if (entity instanceof LivingEntity livingEntity) { 59 | applyFallThroughVoidEffects(livingEntity); 60 | } 61 | 62 | final var entitiesToTeleport = new ArrayList(); 63 | entitiesToTeleport.add(entity); 64 | if (entity.isVehicle()) { 65 | entitiesToTeleport.addAll(entity.getPassengers()); 66 | entity.ejectPassengers(); 67 | } 68 | 69 | final var vehicle = entity.getVehicle(); 70 | if (vehicle != null) { 71 | entitiesToTeleport.add(vehicle); 72 | entity.stopRiding(); 73 | } 74 | 75 | entitiesToTeleport.forEach(teleportedEntity -> { 76 | if (isAllowedEntity(teleportedEntity)) { 77 | if (teleportedEntity instanceof ServerPlayerAccessor player) { 78 | player.setIsChangingDimension(true); 79 | } 80 | final var teleportedEntityData = Balm.hooks().getPersistentData(teleportedEntity); 81 | final var returnToGrounded = ForgivingVoidConfig.getActive().returnToLastGrounded; 82 | final var lastGroundedPos = teleportedEntityData.getLong("LastGroundedPos").map(BlockPos::of).orElseGet(teleportedEntity::blockPosition); 83 | final var x = returnToGrounded ? lastGroundedPos.getX() + 0.5f : teleportedEntity.getX(); 84 | final var y = ForgivingVoidConfig.getActive().fallingHeight; 85 | final var z = returnToGrounded ? lastGroundedPos.getZ() + 0.5f : teleportedEntity.getZ(); 86 | teleportedEntity.teleportTo(x, y, z); 87 | teleportedEntityData.putBoolean("ForgivingVoidIsFalling", true); 88 | } 89 | }); 90 | 91 | if (vehicle != null) { 92 | entity.startRiding(vehicle); 93 | } 94 | } else if (persistentData.getBooleanOr("ForgivingVoidIsFalling", false)) { 95 | // LivingFallEvent is not called when the player falls into water or is flying, so reset it manually - and give no damage at all. 96 | if (hasLanded(entity) || isOrMayFly(entity)) { 97 | persistentData.putBoolean("ForgivingVoidIsFalling", false); 98 | if (entity instanceof ServerPlayerAccessor player) { 99 | player.setIsChangingDimension(false); 100 | } 101 | return; 102 | } 103 | 104 | if (ForgivingVoidConfig.getActive().disableVanillaAntiCheatWhileFalling && entity instanceof ServerPlayerAccessor player) { 105 | // Vanilla's AntiCheat is triggers on falling and teleports, even in Vanilla. 106 | // So I'll just disable it until the player lands, so it doesn't look like it's my mod causing the issue. 107 | player.setIsChangingDimension(true); 108 | } 109 | } 110 | } 111 | 112 | private static void applyFallThroughVoidEffects(LivingEntity entity) { 113 | for (String effectString : ForgivingVoidConfig.getActive().fallThroughVoidEffects) { 114 | String[] parts = effectString.split("\\|"); 115 | Identifier registryName = Identifier.tryParse(parts[0]); 116 | if (registryName != null) { 117 | final var holder = BuiltInRegistries.MOB_EFFECT.get(registryName); 118 | if (holder.isPresent()) { 119 | int duration = tryParseInt(parts.length >= 2 ? parts[1] : null, 600); 120 | int amplifier = tryParseInt(parts.length >= 3 ? parts[2] : null, 0); 121 | entity.addEffect(new MobEffectInstance(holder.get(), duration, amplifier)); 122 | } else { 123 | ForgivingVoid.logger.info("Invalid fall through void effect '{}'", parts[0]); 124 | } 125 | } else { 126 | ForgivingVoid.logger.info("Invalid fall through void effect '{}'", parts[0]); 127 | } 128 | } 129 | } 130 | 131 | private static int tryParseInt(@Nullable String text, int defaultVal) { 132 | if (text != null) { 133 | try { 134 | return Integer.parseInt(text); 135 | } catch (NumberFormatException e) { 136 | return defaultVal; 137 | } 138 | } 139 | return defaultVal; 140 | } 141 | 142 | private static boolean isAllowedEntity(Entity entity) { 143 | if (entity.level().isClientSide()) { 144 | return false; 145 | } 146 | 147 | final var entityAllowList = ForgivingVoidConfig.getActive().entityAllowList; 148 | final var entityId = BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()); 149 | if (entityAllowList.isEmpty() && entity instanceof Player) { 150 | return true; 151 | } 152 | 153 | if (ForgivingVoidConfig.getActive().tridentForgiveness && entity instanceof ThrownTrident trident) { 154 | final var loyalty = trident.getEntityData().get(ThrownTridentAccessor.getIdLoyalty()); 155 | //noinspection UnreachableCode 156 | if (loyalty > 0) { 157 | return true; 158 | } 159 | } 160 | 161 | return entityAllowList.contains(entityId); 162 | } 163 | 164 | public static final Set FALL_CATCHING_BLOCKS = Set.of(Blocks.COBWEB); 165 | 166 | private static boolean hasLanded(Entity entity) { 167 | if (entity.onGround() || entity.isInWater() || entity.isInLava()) { 168 | return true; 169 | } 170 | 171 | final var landedOnState = entity.level().getBlockState(entity.blockPosition()); 172 | return FALL_CATCHING_BLOCKS.contains(landedOnState.getBlock()); 173 | } 174 | 175 | private static boolean isOrMayFly(Entity entity) { 176 | if (entity instanceof LivingEntity livingEntity && livingEntity.isFallFlying()) { 177 | return true; 178 | } 179 | 180 | if (!(entity instanceof Player player)) { 181 | return false; 182 | } 183 | 184 | return player.getAbilities().flying || player.getAbilities().mayfly; 185 | } 186 | 187 | public static float onLivingEntityFall(LivingEntity entity, float fallDamage) { 188 | if (isAllowedEntity(entity)) { 189 | CompoundTag persistentData = Balm.hooks().getPersistentData(entity); 190 | if (persistentData.getBooleanOr("ForgivingVoidIsFalling", false)) { 191 | final var config = ForgivingVoidConfig.getActive(); 192 | final var damage = calculateFallDamage(config, entity); 193 | 194 | if (entity instanceof ServerPlayerAccessor player) { 195 | player.setIsChangingDimension(false); 196 | } 197 | 198 | return damage; 199 | } 200 | } 201 | 202 | return fallDamage; 203 | } 204 | 205 | private static float calculateFallDamage(ForgivingVoidConfig config, LivingEntity entity) { 206 | float damage = config.damageOnFall; 207 | // We normalize percentages if the user accidentally set a value out of 100. 208 | if (config.damageOnFallMode != DamageOnFallMode.ABSOLUTE && damage > 1) { 209 | damage = damage / 100f; 210 | } 211 | if (config.damageOnFallMode == DamageOnFallMode.RELATIVE_CURRENT) { 212 | damage = entity.getHealth() * damage; 213 | } else if (config.damageOnFallMode == DamageOnFallMode.RELATIVE_MAX) { 214 | damage = entity.getMaxHealth() * damage; 215 | } 216 | if (config.preventDeath && entity.getHealth() - damage <= 0) { 217 | damage = entity.getHealth() - 1f; 218 | } 219 | return damage; 220 | } 221 | 222 | private static boolean fireForgivingVoidEvent(Entity entity) { 223 | ForgivingVoidFallThroughEvent event = new ForgivingVoidFallThroughEvent(entity); 224 | ForgivingVoidFallThroughEvent.EVENT.invoker().accept(event); 225 | return !event.isCanceled(); 226 | } 227 | 228 | private static boolean isEnabledForDimension(ResourceKey dimensionKey) { 229 | if (dimensionKey == Level.OVERWORLD) { 230 | return ForgivingVoidConfig.getActive().triggerInOverworld; 231 | } else if (dimensionKey == Level.END) { 232 | return ForgivingVoidConfig.getActive().triggerInEnd; 233 | } else if (dimensionKey == Level.NETHER) { 234 | return ForgivingVoidConfig.getActive().triggerInNether; 235 | } else { 236 | final Identifier dimension = dimensionKey.identifier(); 237 | final var dimensionAllowList = ForgivingVoidConfig.getActive().dimensionAllowList; 238 | final var dimensionDenyList = ForgivingVoidConfig.getActive().dimensionDenyList; 239 | if (!dimensionAllowList.isEmpty() && !dimensionAllowList.contains(dimension)) { 240 | return false; 241 | } else { 242 | return !dimensionDenyList.contains(dimension); 243 | } 244 | } 245 | } 246 | 247 | } 248 | --------------------------------------------------------------------------------