commit 1da1a821433e059acba18dc46bbbfc0a06804ec4 Author: R1FoM Date: Wed May 13 19:16:59 2026 +0200 туту туттуру diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..badc996 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,48 @@ +name: Build + +on: + push: + branches: + - main + - master + tags: + - "v*" + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Java 21 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: "21" + + - name: Set up Gradle caching + uses: gradle/actions/setup-gradle@v4 + + - name: Make Gradle wrapper executable + run: chmod +x ./gradlew + + - name: Build mod + run: ./gradlew build -Dorg.gradle.java.home="$JAVA_HOME" -Dorg.gradle.java.installations.paths="$JAVA_HOME" + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: ftbchunksaerospace-${{ github.ref_name }} + path: | + build/libs/*.jar + !build/libs/*-sources.jar + !build/libs/*-javadoc.jar + if-no-files-found: error + retention-days: 14 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..d8dbb6d --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,131 @@ +name: Release + +on: + release: + types: + - published + workflow_dispatch: + inputs: + tag_name: + description: Git tag to release, for example v1.0.0 + required: true + type: string + release_name: + description: Optional release title + required: false + type: string + prerelease: + description: Mark the release as prerelease when creating it from workflow_dispatch + required: false + default: false + type: boolean + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Java 21 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: "21" + + - name: Set up Gradle caching + uses: gradle/actions/setup-gradle@v4 + + - name: Make Gradle wrapper executable + run: chmod +x ./gradlew + + - name: Build mod + run: ./gradlew build -Dorg.gradle.java.home="$JAVA_HOME" -Dorg.gradle.java.installations.paths="$JAVA_HOME" + + - name: Resolve release metadata + id: metadata + shell: bash + run: | + if [[ "${{ github.event_name }}" == "release" ]]; then + echo "tag=${{ github.event.release.tag_name }}" >> "$GITHUB_OUTPUT" + echo "name=${{ github.event.release.name }}" >> "$GITHUB_OUTPUT" + echo "prerelease=${{ github.event.release.prerelease }}" >> "$GITHUB_OUTPUT" + else + echo "tag=${{ inputs.tag_name }}" >> "$GITHUB_OUTPUT" + echo "name=${{ inputs.release_name }}" >> "$GITHUB_OUTPUT" + echo "prerelease=${{ inputs.prerelease }}" >> "$GITHUB_OUTPUT" + fi + + - name: Create release when started manually + if: github.event_name == 'workflow_dispatch' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG_NAME: ${{ steps.metadata.outputs.tag }} + RELEASE_NAME: ${{ steps.metadata.outputs.name }} + PRERELEASE: ${{ steps.metadata.outputs.prerelease }} + shell: bash + run: | + if gh release view "$TAG_NAME" > /dev/null 2>&1; then + exit 0 + fi + + title="$RELEASE_NAME" + if [[ -z "$title" ]]; then + title="$TAG_NAME" + fi + + args=("$TAG_NAME" "--title" "$title" "--generate-notes") + if [[ "$PRERELEASE" == "true" ]]; then + args+=("--prerelease") + fi + + gh release create "${args[@]}" + + - name: Collect release jars + id: jars + shell: bash + run: | + shopt -s nullglob + jars=() + for jar in build/libs/*.jar; do + case "$jar" in + *-sources.jar|*-javadoc.jar) + continue + ;; + esac + jars+=("$jar") + done + + if [[ ${#jars[@]} -eq 0 ]]; then + echo "No release jars found in build/libs" >&2 + exit 1 + fi + + printf 'files<> "$GITHUB_OUTPUT" + printf '%s\n' "${jars[@]}" >> "$GITHUB_OUTPUT" + printf 'EOF\n' >> "$GITHUB_OUTPUT" + + - name: Upload release assets + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG_NAME: ${{ steps.metadata.outputs.tag }} + RELEASE_FILES: ${{ steps.jars.outputs.files }} + shell: bash + run: | + mapfile -t jars <<< "$RELEASE_FILES" + gh release upload "$TAG_NAME" "${jars[@]}" --clobber + + - name: Upload workflow artifacts + uses: actions/upload-artifact@v4 + with: + name: ftbchunksaerospace-release-${{ steps.metadata.outputs.tag }} + path: | + build/libs/*.jar + !build/libs/*-sources.jar + !build/libs/*-javadoc.jar + if-no-files-found: error + retention-days: 30 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8949363 --- /dev/null +++ b/.gitignore @@ -0,0 +1,64 @@ +### Gradle +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/**/build/ + +### IntelliJ IDEA +.idea/ +*.iws +*.iml +*.ipr +out/ +!**/src/**/out/ + +.run/ + +### Eclipse +.apt_generated +.classpath +.eclipse/ +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/**/bin/ + +### VS Code +.vscode/* +!.vscode/launch.json +!.vscode/settings.json +!.vscode/tasks.json + +### Mac OS +.DS_Store + +### Minecraft Modding +run/ +run2/ +run-data/ +run-gametest/ +run-server/ +!**/src/**/run/ +**/src/generated/**/.cache/ +bin/ +config/ +crash-reports/ +defaultconfigs/ +debug/ +downloads/ +local/ +logs/ +mods/ +resourcepacks/ +saved-hotbars/ +saves/ +imgui.ini +options.txt +servers.dat +usercache.json +usernamecache.json +repo/ +!**/src/**/repo/ \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..513468d --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,103 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "java", + "request": "launch", + "name": "Client", + "presentation": { + "group": "Mod Development - dontbreaksable", + "order": 0 + }, + "projectName": "dontbreaksable", + "mainClass": "net.neoforged.devlaunch.Main", + "args": [ + "@F:\\mods\\dontbreaksable\\build\\moddev\\clientRunProgramArgs.txt" + ], + "vmArgs": [ + "@F:\\mods\\dontbreaksable\\build\\moddev\\clientRunVmArgs.txt", + "-Dfml.modFolders\u003ddontbreaksable%%F:\\mods\\dontbreaksable\\bin\\main" + ], + "cwd": "${workspaceFolder}\\run", + "env": {}, + "console": "internalConsole", + "shortenCommandLine": "none" + }, + { + "type": "java", + "request": "launch", + "name": "Data", + "presentation": { + "group": "Mod Development - dontbreaksable", + "order": 1 + }, + "projectName": "dontbreaksable", + "mainClass": "net.neoforged.devlaunch.Main", + "args": [ + "@F:\\mods\\dontbreaksable\\build\\moddev\\dataRunProgramArgs.txt" + ], + "vmArgs": [ + "@F:\\mods\\dontbreaksable\\build\\moddev\\dataRunVmArgs.txt", + "-Dfml.modFolders\u003ddontbreaksable%%F:\\mods\\dontbreaksable\\bin\\main" + ], + "cwd": "${workspaceFolder}\\run", + "env": {}, + "console": "internalConsole", + "shortenCommandLine": "none" + }, + { + "type": "java", + "request": "launch", + "name": "GameTestServer", + "presentation": { + "group": "Mod Development - dontbreaksable", + "order": 2 + }, + "projectName": "dontbreaksable", + "mainClass": "net.neoforged.devlaunch.Main", + "args": [ + "@F:\\mods\\dontbreaksable\\build\\moddev\\gameTestServerRunProgramArgs.txt" + ], + "vmArgs": [ + "@F:\\mods\\dontbreaksable\\build\\moddev\\gameTestServerRunVmArgs.txt", + "-Dfml.modFolders\u003ddontbreaksable%%F:\\mods\\dontbreaksable\\bin\\main" + ], + "cwd": "${workspaceFolder}\\run", + "env": {}, + "console": "internalConsole", + "shortenCommandLine": "none" + }, + { + "type": "java", + "request": "launch", + "name": "Server", + "presentation": { + "group": "Mod Development - dontbreaksable", + "order": 3 + }, + "projectName": "dontbreaksable", + "mainClass": "net.neoforged.devlaunch.Main", + "args": [ + "@F:\\mods\\dontbreaksable\\build\\moddev\\serverRunProgramArgs.txt" + ], + "vmArgs": [ + "@F:\\mods\\dontbreaksable\\build\\moddev\\serverRunVmArgs.txt", + "-Dfml.modFolders\u003ddontbreaksable%%F:\\mods\\dontbreaksable\\bin\\main" + ], + "cwd": "${workspaceFolder}\\run", + "env": {}, + "console": "internalConsole", + "shortenCommandLine": "none" + } + ], + "compounds": [ + { + "name": "Two Clients", + "preLaunchTask": "prepareClientRun", + "configurations": [ + "Client", + "Client Alt" + ] + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..b8c1544 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "java.import.gradle.wrapper.enabled": true, + "java.project.outputPath": "build/vscode-java", + "java.debug.settings.forceBuildBeforeLaunch": false, + "java.debug.settings.onBuildFailureProceed": false, + "java.configuration.updateBuildConfiguration": "interactive" +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..e1b4fa4 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,23 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "prepareClientRun", + "type": "process", + "command": "${workspaceFolder}/.vscode/prepareClientRun.sh", + "windows": { + "command": "${workspaceFolder}\\.vscode\\prepareClientRun.bat" + }, + "linux": { + "command": "${workspaceFolder}/.vscode/prepareClientRun.sh" + }, + "osx": { + "command": "${workspaceFolder}/.vscode/prepareClientRun.sh" + }, + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [] + } + ] +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3058a22 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +# MIT License + +Copyright (c) 2026 maks-gaming + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..78a5b47 --- /dev/null +++ b/build.gradle @@ -0,0 +1,242 @@ +plugins { + id 'java-library' + id 'maven-publish' + id 'net.neoforged.moddev' version '2.0.141' + id 'idea' +} + +tasks.named('wrapper', Wrapper).configure { + distributionType = Wrapper.DistributionType.BIN +} + +version = mod_version +group = mod_group_id + +configurations { + runtimeClasspath.extendsFrom localRuntime +} + +sourceSets.main.resources { + srcDir('src/generated/resources') + exclude('**/*.bbmodel') + exclude('src/generated/**/.cache') +} + +repositories { + mavenCentral() + + exclusiveContent { + forRepository { + maven { + url = 'https://maven.blamejared.com/' + name = 'Jared Maven' + } + } + filter { + includeGroup('foundry.veil') + includeGroup('gg.moonflower') + includeGroup('io.github.ocelot') + } + } + + exclusiveContent { + forRepository { + maven { + url = 'https://raw.githubusercontent.com/Fuzss/modresources/main/maven/' + name = 'Fuzs Mod Resources' + } + } + filter { + includeGroup('fuzs.forgeconfigapiport') + } + } + + exclusiveContent { + forRepository { + maven { + url = 'https://maven.ryanhcode.dev/releases' + name = 'RyanHCode Maven' + } + } + filter { + includeGroup('dev.ryanhcode.sable') + includeGroup('dev.ryanhcode.sable-companion') + } + } + + + + +} + +base { + archivesName = mod_id +} + +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} + +neoForge { + version = project.neo_version + + parchment { + mappingsVersion = project.parchment_mappings_version + minecraftVersion = project.parchment_minecraft_version + } + + runs { + client { + client() + systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id + } + + server { + server() + programArgument '--nogui' + systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id + } + + gameTestServer { + type = 'gameTestServer' + systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id + } + + data { + data() + programArguments.addAll '--mod', project.mod_id, '--all', '--output', file('src/generated/resources/').getAbsolutePath(), '--existing', file('src/main/resources/').getAbsolutePath() + } + + configureEach { + systemProperty 'forge.logging.markers', 'REGISTRIES' + logLevel = org.slf4j.event.Level.DEBUG + } + } + + mods { + "${mod_id}" { + sourceSet(sourceSets.main) + } + } +} + +dependencies { + compileOnly("dev.ryanhcode.sable:sable-common-${project.minecraft_version}:${project.sable_version}") + + localRuntime("dev.ryanhcode.sable:sable-neoforge-${project.minecraft_version}:${project.sable_version}") { + exclude group: 'com.simibubi.create', module: "create-${project.minecraft_version}" + exclude group: 'net.createmod.ponder', module: 'ponder-neoforge' + exclude group: 'dev.engine-room.flywheel', module: "flywheel-neoforge-${project.minecraft_version}" + exclude group: 'com.tterrag.registrate', module: 'Registrate' + } +} + +def rewriteVsCodeProgramArgs = { File sourceFile, File targetFile, File gameDir, String username -> + def rewritten = [] + def replacedGameDir = false + def replacedUsername = false + def lines = sourceFile.readLines('UTF-8') + + for (int i = 0; i < lines.size(); i++) { + if (lines[i] == '--gameDir' && i + 1 < lines.size()) { + rewritten << '--gameDir' + rewritten << gameDir.absolutePath + i++ + replacedGameDir = true + continue + } + + if (lines[i] == '--username' && i + 1 < lines.size()) { + rewritten << '--username' + rewritten << username + i++ + replacedUsername = true + continue + } + + rewritten << lines[i] + } + + if (!replacedGameDir) { + rewritten << '--gameDir' + rewritten << gameDir.absolutePath + } + + if (!replacedUsername) { + rewritten << '--username' + rewritten << username + } + + targetFile.parentFile.mkdirs() + targetFile.text = rewritten.join(System.lineSeparator()) + System.lineSeparator() +} + +tasks.register('prepareVsCodeClientRuns') { + dependsOn tasks.named('classes'), tasks.named('prepareClientRun') + + inputs.file(layout.buildDirectory.file('moddev/clientRunProgramArgs.txt')) + outputs.files( + layout.buildDirectory.file('moddev/vscodeClientRunProgramArgs.txt'), + layout.buildDirectory.file('moddev/vscodeClientAltRunProgramArgs.txt') + ) + + doLast { + file('run').mkdirs() + file('run2').mkdirs() + file('run-data').mkdirs() + file('run-gametest').mkdirs() + file('run-server').mkdirs() + + def modDevDir = layout.buildDirectory.dir('moddev').get().asFile + def sourceFile = new File(modDevDir, 'clientRunProgramArgs.txt') + + rewriteVsCodeProgramArgs(sourceFile, new File(modDevDir, 'vscodeClientRunProgramArgs.txt'), file('run'), 'DevMain') + rewriteVsCodeProgramArgs(sourceFile, new File(modDevDir, 'vscodeClientAltRunProgramArgs.txt'), file('run2'), 'DevAlt') + } +} + +def generateModMetadata = tasks.register('generateModMetadata', ProcessResources) { + def replaceProperties = [ + minecraft_version : minecraft_version, + minecraft_version_range: minecraft_version_range, + neo_version : neo_version, + loader_version_range : loader_version_range, + mod_id : mod_id, + mod_name : mod_name, + mod_license : mod_license, + mod_version : mod_version + ] + + inputs.properties replaceProperties + expand replaceProperties + from 'src/main/templates' + into 'build/generated/sources/modMetadata' +} + +sourceSets.main.resources.srcDir generateModMetadata +neoForge.ideSyncTask generateModMetadata + +publishing { + publications { + register('mavenJava', MavenPublication) { + from components.java + } + } + repositories { + maven { + url = "file://${project.projectDir}/repo" + } + } +} + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' +} + +idea { + module { + downloadSources = true + downloadJavadoc = true + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..76ed708 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,22 @@ +org.gradle.jvmargs=-Xmx2G +org.gradle.java.installations.auto-download=true +org.gradle.daemon=true +org.gradle.parallel=true +org.gradle.caching=true +org.gradle.configuration-cache=false + +parchment_minecraft_version=1.21.1 +parchment_mappings_version=2024.11.17 + +minecraft_version=1.21.1 +minecraft_version_range=[1.21.1] +neo_version=21.1.227 +loader_version_range=[1,) + +mod_id=dontbreaksable +mod_name=Dont Break Sable +mod_license=MIT +mod_version=1.0.0 +mod_group_id=dev.mgcode.dontbreaksable + +sable_version=1.1.3 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..1b33c55 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..23449a2 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..23d15a9 --- /dev/null +++ b/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..db3a6ac --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%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" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..857cec3 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,12 @@ +pluginManagement { + repositories { + gradlePluginPortal() + mavenCentral() + } +} + +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0' +} + +rootProject.name = 'dontbreaksable' \ No newline at end of file diff --git a/src/main/java/dev/mgcode/dontbreaksable/DontBreakSableConfig.java b/src/main/java/dev/mgcode/dontbreaksable/DontBreakSableConfig.java new file mode 100644 index 0000000..43e51ce --- /dev/null +++ b/src/main/java/dev/mgcode/dontbreaksable/DontBreakSableConfig.java @@ -0,0 +1,64 @@ +package dev.mgcode.dontbreaksable; + +import net.neoforged.neoforge.common.ModConfigSpec; + +import java.util.List; + +public final class DontBreakSableConfig { + static final ModConfigSpec COMMON_SPEC; + public static final Common COMMON; + + static { + final ModConfigSpec.Builder commonBuilder = new ModConfigSpec.Builder(); + COMMON = new Common(commonBuilder); + COMMON_SPEC = commonBuilder.build(); + } + + private DontBreakSableConfig() { + } + + public static final class Common { + private final ModConfigSpec.BooleanValue allowBreaking; + private final ModConfigSpec.ConfigValue> breakWhitelist; + private final ModConfigSpec.BooleanValue allowInteracting; + private final ModConfigSpec.BooleanValue allowPlacing; + + private Common(final ModConfigSpec.Builder builder) { + builder.comment("Sable Sub-Level Protection Settings").push("protection"); + + allowBreaking = builder + .comment("Allow breaking blocks on sub-levels. Default: false.") + .define("allowBreaking", false); + + breakWhitelist = builder + .comment("List of blocks that can be broken even if allowBreaking is false. Example: [\"minecraft:glass\"]") + .defineList("breakWhitelist", List.of(), o -> o instanceof String); + + allowInteracting = builder + .comment("Allow interacting with blocks (doors, levers, etc.) on sub-levels. Default: true.") + .define("allowInteracting", true); + + allowPlacing = builder + .comment("Allow placing blocks on sub-levels. Default: false.") + .define("allowPlacing", false); + + builder.pop(); + } + + public boolean allowBreaking() { + return allowBreaking.get(); + } + + public List breakWhitelist() { + return breakWhitelist.get(); + } + + public boolean allowInteracting() { + return allowInteracting.get(); + } + + public boolean allowPlacing() { + return allowPlacing.get(); + } + } +} diff --git a/src/main/java/dev/mgcode/dontbreaksable/DontBreakSableMod.java b/src/main/java/dev/mgcode/dontbreaksable/DontBreakSableMod.java new file mode 100644 index 0000000..815d86f --- /dev/null +++ b/src/main/java/dev/mgcode/dontbreaksable/DontBreakSableMod.java @@ -0,0 +1,21 @@ +package dev.mgcode.dontbreaksable; + +import com.mojang.logging.LogUtils; +import net.neoforged.fml.ModContainer; +import net.neoforged.fml.common.Mod; +import net.neoforged.fml.config.ModConfig; +import net.neoforged.neoforge.common.NeoForge; +import org.slf4j.Logger; + +@Mod(DontBreakSableMod.MOD_ID) +public final class DontBreakSableMod { + public static final String MOD_ID = "dontbreaksable"; + public static final Logger LOGGER = LogUtils.getLogger(); + + public DontBreakSableMod(final ModContainer modContainer) { + modContainer.registerConfig(ModConfig.Type.COMMON, DontBreakSableConfig.COMMON_SPEC); + + NeoForge.EVENT_BUS.register(new SableProtectionHandler()); + LOGGER.info("Loading mod {} with Sable sub-level protections", MOD_ID); + } +} diff --git a/src/main/java/dev/mgcode/dontbreaksable/SableProtectionHandler.java b/src/main/java/dev/mgcode/dontbreaksable/SableProtectionHandler.java new file mode 100644 index 0000000..b5e8da8 --- /dev/null +++ b/src/main/java/dev/mgcode/dontbreaksable/SableProtectionHandler.java @@ -0,0 +1,112 @@ +package dev.mgcode.dontbreaksable; + +import dev.ryanhcode.sable.Sable; +import dev.ryanhcode.sable.sublevel.SubLevel; +import net.minecraft.ChatFormatting; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.Level; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.neoforge.event.entity.player.PlayerInteractEvent; +import net.neoforged.neoforge.event.level.BlockEvent; + +public final class SableProtectionHandler { + private static final String ACTION_DENIED_BREAK = "message.dontbreaksable.break_denied"; + private static final String ACTION_DENIED_PLACE = "message.dontbreaksable.place_denied"; + private static final String ACTION_DENIED_INTERACT = "message.dontbreaksable.interact_denied"; + + @SubscribeEvent + public void onRightClickBlock(final PlayerInteractEvent.RightClickBlock event) { + if (!(event.getEntity() instanceof ServerPlayer player)) { + return; + } + + if (isExempt(player)) { + return; + } + + if (isOnSubLevel(player.level(), event.getPos())) { + if (!DontBreakSableConfig.COMMON.allowInteracting()) { + player.displayClientMessage(Component.translatable(ACTION_DENIED_INTERACT).withStyle(ChatFormatting.RED), true); + event.setCanceled(true); + } + } + } + + @SubscribeEvent + public void onLeftClickBlock(final PlayerInteractEvent.LeftClickBlock event) { + if (!(event.getEntity() instanceof ServerPlayer player)) { + return; + } + + if (isExempt(player)) { + return; + } + + if (isOnSubLevel(player.level(), event.getPos())) { + BlockState state = player.level().getBlockState(event.getPos()); + if (!canBreakBlock(state)) { + player.displayClientMessage(Component.translatable(ACTION_DENIED_BREAK).withStyle(ChatFormatting.RED), true); + event.setCanceled(true); + } + } + } + + @SubscribeEvent + public void onBreakBlock(final BlockEvent.BreakEvent event) { + if (!(event.getPlayer() instanceof ServerPlayer player)) { + return; + } + + if (isExempt(player)) { + return; + } + + if (isOnSubLevel(player.level(), event.getPos())) { + if (!canBreakBlock(event.getState())) { + player.displayClientMessage(Component.translatable(ACTION_DENIED_BREAK).withStyle(ChatFormatting.RED), true); + event.setCanceled(true); + } + } + } + + @SubscribeEvent + public void onPlaceBlock(final BlockEvent.EntityPlaceEvent event) { + if (!(event.getEntity() instanceof ServerPlayer player)) { + return; + } + + if (isExempt(player)) { + return; + } + + if (isOnSubLevel(player.level(), event.getPos())) { + if (!DontBreakSableConfig.COMMON.allowPlacing()) { + player.displayClientMessage(Component.translatable(ACTION_DENIED_PLACE).withStyle(ChatFormatting.RED), true); + event.setCanceled(true); + } + } + } + + private boolean canBreakBlock(BlockState state) { + if (DontBreakSableConfig.COMMON.allowBreaking()) { + return true; + } + ResourceLocation registryName = BuiltInRegistries.BLOCK.getKey(state.getBlock()); + if (registryName == null) return false; + return DontBreakSableConfig.COMMON.breakWhitelist().contains(registryName.toString()); + } + + private boolean isOnSubLevel(final Level level, final BlockPos physicalBlockPos) { + final SubLevel subLevel = Sable.HELPER.getContaining(level, physicalBlockPos); + return subLevel != null; + } + + private boolean isExempt(ServerPlayer player) { + return player.isCreative() && player.hasPermissions(2); + } +} diff --git a/src/main/resources/assets/dontbreaksable/lang/en_us.json b/src/main/resources/assets/dontbreaksable/lang/en_us.json new file mode 100644 index 0000000..a45777f --- /dev/null +++ b/src/main/resources/assets/dontbreaksable/lang/en_us.json @@ -0,0 +1,5 @@ +{ + "message.dontbreaksable.break_denied": "You cannot break blocks on this sub-level.", + "message.dontbreaksable.place_denied": "You cannot place blocks on this sub-level.", + "message.dontbreaksable.interact_denied": "You cannot interact with blocks on this sub-level." +} \ No newline at end of file diff --git a/src/main/resources/assets/dontbreaksable/lang/ru_ru.json b/src/main/resources/assets/dontbreaksable/lang/ru_ru.json new file mode 100644 index 0000000..410f04b --- /dev/null +++ b/src/main/resources/assets/dontbreaksable/lang/ru_ru.json @@ -0,0 +1,5 @@ +{ + "message.dontbreaksable.break_denied": "Вы не можете ломать блоки на этой конструкции.", + "message.dontbreaksable.place_denied": "Вы не можете ставить блоки на этой конструкции.", + "message.dontbreaksable.interact_denied": "Вы не можете взаимодействовать с блоками на этой конструкции." +} \ No newline at end of file diff --git a/src/main/resources/mod_logo.png b/src/main/resources/mod_logo.png new file mode 100644 index 0000000..da0bd26 Binary files /dev/null and b/src/main/resources/mod_logo.png differ diff --git a/src/main/templates/META-INF/neoforge.mods.toml b/src/main/templates/META-INF/neoforge.mods.toml new file mode 100644 index 0000000..452765a --- /dev/null +++ b/src/main/templates/META-INF/neoforge.mods.toml @@ -0,0 +1,33 @@ +modLoader = "javafml" +loaderVersion = "${loader_version_range}" +license = "${mod_license}" + +[[mods]] +modId = "${mod_id}" +version = "${mod_version}" +displayName = "${mod_name}" +logoFile = "mod_logo.png" +description = ''' +Protects Sable assemblies and spawns on NeoForge 1.21.1. +''' + +[[dependencies."${mod_id}"]] +modId = "neoforge" +type = "required" +versionRange = "[${neo_version},)" +ordering = "NONE" +side = "BOTH" + +[[dependencies."${mod_id}"]] +modId = "minecraft" +type = "required" +versionRange = "${minecraft_version_range}" +ordering = "NONE" +side = "BOTH" + +[[dependencies."${mod_id}"]] +modId = "sable" +type = "required" +versionRange = "[1.1.3,)" +ordering = "AFTER" +side = "BOTH"