commit 32ec10cfebcd56b8a30925ab204db82896f56b45 Author: 3944Realms Date: Tue Oct 14 20:42:25 2025 +0800 初始化项目 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..63c3cde1 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,25 @@ +name: Build + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true + + - name: Setup JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Build with Gradle + run: ./gradlew build \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..31d25505 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# eclipse +bin +*.launch +.settings +.metadata +.classpath +.project + +# idea +out +*.ipr +*.iws +*.iml +.idea + +# gradle +build +.gradle + +# other +eclipse +run +runs +run-data + +repo \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..2e0f5f28 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025-2026 R3944Realms + +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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..535f3ac4 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# Lib39 + +**Lib39** is a general-purpose dependency library for Minecraft mods. +It provides utility methods and core functionality that other mods can build upon. diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..c885be12 --- /dev/null +++ b/build.gradle @@ -0,0 +1,275 @@ +//file:noinspection GroovyAssignabilityCheck +plugins { + id 'java' + id 'idea' + id 'java-library' + id 'maven-publish' + id 'com.github.johnrengelman.shadow' version '8.1.1' + id 'net.neoforged.moddev.legacyforge' version '2.0.103' +} + +tasks.named('wrapper', Wrapper).configure { + // Define wrapper values here so as to not have to always do so when updating gradlew.properties. + // Switching this to Wrapper.DistributionType.ALL will download the full gradle sources that comes with + // documentation attached on cursor hover of gradle classes and methods. However, this comes with increased + // file size for Gradle. If you do switch this to ALL, run the Gradle wrapper task twice afterwards. + // (Verify by checking gradle/wrapper/gradle-wrapper.properties to see if distributionUrl now points to `-all`) + distributionType = Wrapper.DistributionType.BIN +} + +version = "${minecraft_version}-${mod_version}" +group = mod_group_id + +repositories { + mavenLocal() + maven { url = "https://libraries.minecraft.net/" } + maven { url = "https://neoforged.forgecdn.net/releases" } + maven { url = "https://neoforged.forgecdn.net/mojang-meta" } + flatDir { + dir "libs" + } + maven { + url "https://cursemaven.com" + content { + includeGroup "curse.maven" + } + } + maven { + // location of the maven that hosts JEI files before January 2023 + name = "Progwml6's maven" + url = "https://dvs1.progwml6.com/files/maven/" + } + maven { + // location of the maven that hosts JEI files since January 2023 + name = "Jared's maven" + url = "https://maven.blamejared.com/" + } + maven { + // location of a maven mirror for JEI files, as a fallback + name = "ModMaven" + url = "https://modmaven.dev" + } + maven { + name 'luck-repo' + url 'https://repo.lucko.me/' + content { + includeModule 'me.lucko', 'spark-api' + } + } + maven { + name "KosmX's maven" + url 'https://maven.kosmx.dev/' + } + maven { + name = "Curios" + url = uri("https://maven.theillusivec4.top/") + } + maven { + name = 'GeckoLib' + url 'https://dl.cloudsmith.io/public/geckolib3/geckolib/maven/' + content { + includeGroupByRegex("software\\.bernie.*") + includeGroup("com.eliotlash.mclib") + } + } + mavenCentral() +} + +base { + archivesName = mod_id +} + +// Mojang ships Java 17 to end users in 1.20.1, so mods should target Java 17. +java.toolchain.languageVersion = JavaLanguageVersion.of(17) + +legacyForge { + // Specify the version of MinecraftForge to use. + version = project.minecraft_version + '-' + project.forge_version + + parchment { + mappingsVersion = project.parchment_mappings_version + minecraftVersion = project.parchment_minecraft_version + } + + // This line is optional. Access Transformers are automatically detected + // accessTransformers = project.files('src/main/resources/META-INF/accesstransformer.cfg') + + // Default run configurations. + // These can be tweaked, removed, or duplicated as needed. + runs { + client { + client() + + // Comma-separated list of namespaces to load gametests from. Empty = all namespaces. + systemProperty 'forge.enabledGameTestNamespaces', project.mod_id + } + clientAuth{ + devLogin = true + client() + systemProperty 'forge.enabledGameTestNamespaces', project.mod_id + } + + server { + server() + programArgument '--nogui' + systemProperty 'forge.enabledGameTestNamespaces', project.mod_id + } + + // This run config launches GameTestServer and runs all registered gametests, then exits. + // By default, the server will crash when no gametests are provided. + // The gametest system is also enabled by default for other run configs under the /test command. + gameTestServer { + type = "gameTestServer" + systemProperty 'forge.enabledGameTestNamespaces', project.mod_id + } + + data { + systemProperty('gradle.task', 'runData') + data() + + // example of overriding the workingDirectory set in configureEach above, uncomment if you want to use it + // gameDirectory = project.file('run-data') + + // Specify the modid for data generation, where to output the resulting resource, and where to look for existing resources. + programArguments.addAll '--mod', project.mod_id, '--all', '--output', file('src/generated/resources/').getAbsolutePath(), '--existing', file('src/main/resources/').getAbsolutePath() + } + + // applies to all the run configs above + configureEach { + // Recommended logging data for a userdev environment + // The markers can be added/remove as needed separated by commas. + // "SCAN": For mods scan. + // "REGISTRIES": For firing of registry events. + // "REGISTRYDUMP": For getting the contents of all registries. + systemProperty 'forge.logging.markers', 'REGISTRIES' + + // Recommended logging level for the console + // You can set various levels here. + // Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels + logLevel = org.slf4j.event.Level.DEBUG + } + } + + mods { + // define mod <-> source bindings + // these are used to tell the game which sources are for which mod + // mostly optional in a single mod project + // but multi mod projects should define one per mod + "${mod_id}" { + sourceSet(sourceSets.main) + } + } +} + +// Include resources generated by data generators. +sourceSets.main.resources { srcDir 'src/generated/resources' } + +// Sets up a dependency configuration called 'localRuntime' and a deobfuscating one called 'modLocalRuntime' +// These configurations should be used instead of 'runtimeOnly' to declare +// a dependency that will be present for runtime testing but that is +// "optional", meaning it will not be pulled by dependents of this mod. +configurations { + runtimeClasspath.extendsFrom localRuntime +} +obfuscation { + createRemappingConfiguration(configurations.localRuntime) +} + +dependencies { + modImplementation("blank:lib39-1.20.1:${lib39_version}") + modRuntimeOnly("curse.maven:debug-utils-forge-783008:5337491") + modImplementation("blank:curtain-1.20.1:1.3.2") + modImplementation("blank:freecam-1.20.1:1.2.1") + modImplementation("blank:carryon-1.20.1:2.1.2.7") + modCompileOnly("mezz.jei:jei-${minecraft_version}-forge-api:${jei_version}") + modCompileOnly("mezz.jei:jei-${minecraft_version}-common-api:${jei_version}") + modRuntimeOnly("mezz.jei:jei-${minecraft_version}-forge:${jei_version}") + modImplementation(jarJar("dev.kosmx.player-anim:player-animation-lib-forge:${player_anim_version}")) + modImplementation(jarJar("curse.maven:bendy-lib-623373:4550371")) + modImplementation(jarJar("software.bernie.geckolib:geckolib-forge-${minecraft_version}:${geckolib_version}")) + implementation(jarJar("com.eliotlash.mclib:mclib:20")) + modCompileOnly("top.theillusivec4.curios:curios-forge:${curios_version}:api") + modImplementation("top.theillusivec4.curios:curios-forge:${curios_version}") + modRuntimeOnly("top.theillusivec4.curios:curios-forge:${curios_version}") + implementation(annotationProcessor("io.github.llamalad7:mixinextras-common:0.4.1")) + modImplementation(jarJar("io.github.llamalad7:mixinextras-forge:0.4.1")) + modRuntimeOnly("curse.maven:spark-361579:4738952") + compileOnly ('me.lucko:spark-api:0.1-SNAPSHOT') +} + +// Uncomment the lines below if you wish to configure mixin. The mixin file should be named modid.mixins.json. +/* +mixin { + add sourceSets.main, "${mod_id}.refmap.json" + config "${mod_id}.mixins.json" +} + +dependencies { + annotationProcessor 'org.spongepowered:mixin:0.8.5:processor' +} + +jar { + manifest.attributes([ + "MixinConfigs": "${mod_id}.mixins.json" + ]) +} +*/ + +// This block of code expands all declared replace properties in the specified resource targets. +// A missing property will result in an error. Properties are expanded using ${} Groovy notation. +var generateModMetadata = tasks.register("generateModMetadata", ProcessResources) { + var replaceProperties = [ + minecraft_version : minecraft_version, + minecraft_version_range : minecraft_version_range, + forge_version : forge_version, + forge_version_range : forge_version_range, + loader_version_range : loader_version_range, + mod_id : mod_id, + mod_name : mod_name, + mod_license : mod_license, + mod_version : mod_version, + mod_authors : mod_authors, + mod_description : mod_description, + mod_credits : mod_credits, + lib39_version : lib39_version, + geckolib_version : geckolib_version, + player_animation_version : player_anim_version, + curios_version : curios_version + + ] + inputs.properties replaceProperties + expand replaceProperties + from "src/main/templates" + into "build/generated/sources/modMetadata" +} +// Include the output of "generateModMetadata" as an input directory for the build +// this works with both building through Gradle and the IDE. +sourceSets.main.resources.srcDir generateModMetadata +// To avoid having to run "generateModMetadata" manually, make it run on every project reload +legacyForge.ideSyncTask generateModMetadata + +// Example configuration to allow publishing using the maven-publish plugin +publishing { + publications { + register('mavenJava', MavenPublication) { + from components.java + } + } + repositories { + maven { + url "file://${project.projectDir}/repo" + } + } +} + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation +} + +// IDEA no longer automatically downloads sources/javadoc jars for dependencies, so we need to explicitly enable the behavior. +idea { + module { + downloadSources = true + downloadJavadoc = true + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000..b3161a27 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,52 @@ +# Sets default memory used for gradle commands. Can be overridden by user or command line properties. +org.gradle.jvmargs=-Xmx3G +org.gradle.daemon=false +org.gradle.parallel=true +org.gradle.caching=true +org.gradle.configuration-cache=false + +#read more on this at https://github.com/neoforged/ModDevGradle?tab=readme-ov-file#better-minecraft-parameter-names--javadoc-parchment +# you can also find the latest versions at: https://parchmentmc.org/docs/getting-started +parchment_minecraft_version=1.20.1 +parchment_mappings_version=2023.09.03 +# Environment Properties +# You can find the latest versions here: https://files.minecraftforge.net/net/minecraftforge/forge/index_1.20.1.html +# The Minecraft version must agree with the Forge version to get a valid artifact +minecraft_version=1.20.1 +# The Minecraft version range can use any release version of Minecraft as bounds. +# Snapshots, pre-releases, and release candidates are not guaranteed to sort properly +# as they do not follow standard versioning conventions. +minecraft_version_range=[1.20.1, 1.21) +# The Forge version must agree with the Minecraft version to get a valid artifact +forge_version=47.1.3 +# The Forge version range can use any version of Forge as bounds +forge_version_range=[47.1.3,) +# The loader version range can only use the major version of FML as bounds +loader_version_range=[47,) + +jei_version=15.20.0.112 +player_anim_version=1.0.2-rc1+1.20 +bend +geckolib_version=4.2.1 +curios_version=5.5.0+1.20.1 +lib39_version=0.0.7 +## Mod Properties +# The unique mod identifier for the mod. Must be lowercase in English locale. Must fit the regex [a-z][a-z0-9_]{1,63} +# Must match the String constant located in the main mod class annotated with @Mod. +mod_id=etorticdungeongame +# The human-readable display name for the mod. +mod_name=Erotic Dungeon Game +# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. +mod_license=CC BY-NC-SA 4.0 +# The mod version. See https://semver.org/ +mod_version=0.0.1 +# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. +# This should match the base package used for the mod sources. +# See https://maven.apache.org/guides/mini/guide-naming-conventions.html +mod_group_id=top.r3944realms.eroticdungeongame +# The authors of the mod. This is a simple text string that is used for display purposes in the mod list. +mod_authors=R3944Realms +# The description of the mod. This is a simple multiline text string that is used for display purposes in the mod list. +mod_description= Hello, Dungeon + +mod_credits= \ 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 00000000..a4b76b95 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 00000000..d4081da4 --- /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-8.14.3-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 00000000..f5feea6d --- /dev/null +++ b/gradlew @@ -0,0 +1,252 @@ +#!/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 +' "$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=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + 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, 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" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000..9d21a218 --- /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=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/libs/carryon-1.20.1-2.1.2.7.jar b/libs/carryon-1.20.1-2.1.2.7.jar new file mode 100644 index 00000000..922380fc Binary files /dev/null and b/libs/carryon-1.20.1-2.1.2.7.jar differ diff --git a/libs/curtain-1.20.1-1.3.2.jar b/libs/curtain-1.20.1-1.3.2.jar new file mode 100644 index 00000000..65caf13f Binary files /dev/null and b/libs/curtain-1.20.1-1.3.2.jar differ diff --git a/libs/freecam-1.20.1-1.2.1.jar b/libs/freecam-1.20.1-1.2.1.jar new file mode 100644 index 00000000..e45d3051 Binary files /dev/null and b/libs/freecam-1.20.1-1.2.1.jar differ diff --git a/libs/lib39-1.20.1-0.0.7.jar b/libs/lib39-1.20.1-0.0.7.jar new file mode 100644 index 00000000..a74f7633 Binary files /dev/null and b/libs/lib39-1.20.1-0.0.7.jar differ diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 00000000..bcbdfe26 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,12 @@ +//file:noinspection GroovyAssignabilityCheck +pluginManagement { + repositories { + mavenLocal() + gradlePluginPortal() + maven { url = 'https://maven.neoforged.net/releases' } + maven { url = 'https://maven.parchmentmc.org' } // Add this line + } +} +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0' +} \ No newline at end of file diff --git a/src/main/java/top/r3944realms/eroticdungeongame/ClientHandler.java b/src/main/java/top/r3944realms/eroticdungeongame/ClientHandler.java new file mode 100644 index 00000000..7c185451 --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/ClientHandler.java @@ -0,0 +1,21 @@ +package top.r3944realms.eroticdungeongame; + +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; +import top.r3944realms.eroticdungeongame.content.animation.AnimationLayers; + +public class ClientHandler { + public static class Game extends ClientHandler { + + } + @net.minecraftforge.fml.common.Mod.EventBusSubscriber(modid = EroticDungeon.MOD_ID, bus = net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT) + + public static class Mod extends ClientHandler { + @SubscribeEvent + public static void onFMLClientSetUp (FMLClientSetupEvent event) { + event.enqueueWork(AnimationLayers::initialize); + } + } + +} diff --git a/src/main/java/top/r3944realms/eroticdungeongame/CommonHandler.java b/src/main/java/top/r3944realms/eroticdungeongame/CommonHandler.java new file mode 100644 index 00000000..5521200b --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/CommonHandler.java @@ -0,0 +1,10 @@ +package top.r3944realms.eroticdungeongame; + +public class CommonHandler { + public static class Game extends CommonHandler { + + } + public static class Mod extends CommonHandler { + + } +} diff --git a/src/main/java/top/r3944realms/eroticdungeongame/EroticDungeon.java b/src/main/java/top/r3944realms/eroticdungeongame/EroticDungeon.java new file mode 100644 index 00000000..97a2cee9 --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/EroticDungeon.java @@ -0,0 +1,31 @@ +package top.r3944realms.eroticdungeongame; + +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import top.r3944realms.eroticdungeongame.content.register.EDGBlockEntities; +import top.r3944realms.eroticdungeongame.content.register.EDGBlocks; +import top.r3944realms.eroticdungeongame.content.register.EDGCreativeTabs; +import top.r3944realms.eroticdungeongame.content.register.EDGEntities; + +@Mod(EroticDungeon.MOD_ID) +public class EroticDungeon { + public static final String MOD_ID = "eroticdungeongame"; + public static final Logger LOGGER = LoggerFactory.getLogger(EroticDungeon.class); + public EroticDungeon() { + IEventBus eventBus = FMLJavaModLoadingContext.get().getModEventBus(); + initialize(eventBus); + } + public static void initialize(IEventBus eventBus) { + EDGBlocks.register(eventBus); + EDGBlockEntities.register(eventBus); + EDGCreativeTabs.register(eventBus); + EDGEntities.register(eventBus); + } + public static ResourceLocation rl(String path) { + return new ResourceLocation(MOD_ID, path); + } +} diff --git a/src/main/java/top/r3944realms/eroticdungeongame/content/animation/AnimationHandler.java b/src/main/java/top/r3944realms/eroticdungeongame/content/animation/AnimationHandler.java new file mode 100644 index 00000000..ec0109b7 --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/content/animation/AnimationHandler.java @@ -0,0 +1,4 @@ +package top.r3944realms.eroticdungeongame.content.animation; + +public class AnimationHandler implements IAnimationHandler { +} diff --git a/src/main/java/top/r3944realms/eroticdungeongame/content/animation/AnimationLayers.java b/src/main/java/top/r3944realms/eroticdungeongame/content/animation/AnimationLayers.java new file mode 100644 index 00000000..033171c8 --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/content/animation/AnimationLayers.java @@ -0,0 +1,18 @@ +package top.r3944realms.eroticdungeongame.content.animation; + +import dev.kosmx.playerAnim.api.layered.IAnimation; +import dev.kosmx.playerAnim.api.layered.ModifierLayer; +import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationFactory; +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.resources.ResourceLocation; +import top.r3944realms.eroticdungeongame.EroticDungeon; + +public class AnimationLayers { + public static final ResourceLocation SEAT_LAYERS = EroticDungeon.rl("seat_layers"); + public static void initialize() { + PlayerAnimationFactory.ANIMATION_DATA_FACTORY.registerFactory(SEAT_LAYERS, 40, AnimationLayers::registerPlayerAnimation); + } + private static IAnimation registerPlayerAnimation(AbstractClientPlayer player) { + return new ModifierLayer<>(); + } +} diff --git a/src/main/java/top/r3944realms/eroticdungeongame/content/animation/AnimationProperties.java b/src/main/java/top/r3944realms/eroticdungeongame/content/animation/AnimationProperties.java new file mode 100644 index 00000000..bf308311 --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/content/animation/AnimationProperties.java @@ -0,0 +1,391 @@ +package top.r3944realms.eroticdungeongame.content.animation; + +import dev.kosmx.playerAnim.core.data.KeyframeAnimation; + +@SuppressWarnings("unused") +public class AnimationProperties { + private int animationIndex; + private int layerLevel; + private float camYaw; + private float camPitch; + private float camRoll; + private float camY; + private float jumpModifier; + private float heightModifier; + private String animationName; + private KeyframeAnimation animation; + private boolean swapRotation; + private boolean invertX; + private boolean invertY; + private boolean invertView; + private Integer up; + private Integer down; + private Integer left; + private Integer right; + private Integer forward; + private Integer back; + + public enum KeyOfSwitchPose { + DOWN, + UP, + LEFT, + RIGHT, + FORWARD, + BACK + } + @SuppressWarnings("unused") + public static class Builder { + private int animationIndex; + private int layerLevel; + private float camYaw; + private float camPitch; + private float camRoll; + private float camY; + private float jumpModifier = 1.0f; + private float heightModifier = 1.0f; + private String animationName; + private KeyframeAnimation animation; + private boolean swapRotation = false; + private boolean invertX = false; + private boolean invertY = false; + private boolean invertView = false; + private Integer up; + private Integer down; + private Integer left; + private Integer right; + private Integer forward; + private Integer back; + + public Builder withAnimationIndex(int animationIndex) { + this.animationIndex = animationIndex; + return this; + } + + public Builder withLayerLevel(int layerLevel) { + this.layerLevel = layerLevel; + return this; + } + + public Builder withCamYaw(float camYaw) { + this.camYaw = camYaw; + return this; + } + + public Builder withCamPitch(float camPitch) { + this.camPitch = camPitch; + return this; + } + + public Builder withCamRoll(float camRoll) { + this.camRoll = camRoll; + return this; + } + + public Builder withCamY(float camY) { + this.camY = camY; + return this; + } + + public Builder withJumpModifier(float jumpModifier) { + this.jumpModifier = jumpModifier; + return this; + } + + public Builder withHeightModifier(float heightModifier) { + this.heightModifier = heightModifier; + return this; + } + + public Builder withAnimationName(String animationName) { + this.animationName = animationName; + return this; + } + + public Builder withAnimation(KeyframeAnimation animation) { + this.animation = animation; + return this; + } + + public Builder withSwapRotation(boolean swapRotation) { + this.swapRotation = swapRotation; + return this; + } + + public Builder withInvertX(boolean invertX) { + this.invertX = invertX; + return this; + } + + public Builder withInvertY(boolean invertY) { + this.invertY = invertY; + return this; + } + + public Builder withInvertView(boolean invertView) { + this.invertView = invertView; + return this; + } + + public Builder withUp(Integer up) { + this.up = up; + return this; + } + + public Builder withDown(Integer down) { + this.down = down; + return this; + } + + public Builder withLeft(Integer left) { + this.left = left; + return this; + } + + public Builder withRight(Integer right) { + this.right = right; + return this; + } + + public Builder withForward(Integer forward) { + this.forward = forward; + return this; + } + + public Builder withBack(Integer back) { + this.back = back; + return this; + } + + public Builder withAdjacent(Integer up, Integer down, Integer left, Integer right, Integer forward, Integer back) { + this.up = up; + this.down = down; + this.left = left; + this.right = right; + this.forward = forward; + this.back = back; + return this; + } + + public AnimationProperties build() { + AnimationProperties properties = new AnimationProperties(); + properties.animationIndex = this.animationIndex; + properties.layerLevel = this.layerLevel; + properties.camYaw = this.camYaw; + properties.camPitch = this.camPitch; + properties.camRoll = this.camRoll; + properties.camY = this.camY; + properties.jumpModifier = this.jumpModifier; + properties.heightModifier = this.heightModifier; + properties.animationName = this.animationName; + properties.animation = this.animation; + properties.swapRotation = this.swapRotation; + properties.invertX = this.invertX; + properties.invertY = this.invertY; + properties.invertView = this.invertView; + properties.up = this.up; + properties.down = this.down; + properties.left = this.left; + properties.right = this.right; + properties.forward = this.forward; + properties.back = this.back; + return properties; + } + } + + // 添加静态方法创建 Builder + public static Builder builder() { + return new Builder(); + } + + public AnimationProperties() { + this.jumpModifier = 1.0f; + this.heightModifier = 1.0f; + this.swapRotation = false; + this.invertX = false; + this.invertY = false; + this.invertView = false; + } + + + public AnimationProperties withLayerLevel(int layerLevel) { + this.layerLevel = layerLevel; + return this; + } + + public AnimationProperties withAnimationIndex(int animationIndex) { + this.animationIndex = animationIndex; + return this; + } + + public AnimationProperties withCamYaw(float camYaw) { + this.camYaw = camYaw; + return this; + } + + public AnimationProperties withCamPitch(float camPitch) { + this.camPitch = camPitch; + return this; + } + + public AnimationProperties withCamRoll(float camRoll) { + this.camRoll = camRoll; + return this; + } + + public AnimationProperties withCamY(float camY) { + this.camY = camY; + return this; + } + + public AnimationProperties withJumpModifier(float jumpModifier) { + this.jumpModifier = jumpModifier; + return this; + } + + public AnimationProperties withHeightModifier(float heightModifier) { + this.heightModifier = heightModifier; + return this; + } + + public AnimationProperties withAnimationName(String animationName) { + this.animationName = animationName; + return this; + } + + public AnimationProperties withAnimation(KeyframeAnimation animation) { + this.animation = animation; + return this; + } + + public AnimationProperties withSwapRotation(boolean swapRotation) { + this.swapRotation = swapRotation; + return this; + } + + public AnimationProperties withInvertX(boolean invertX) { + this.invertX = invertX; + return this; + } + + public AnimationProperties withInvertY(boolean invertY) { + this.invertY = invertY; + return this; + } + + public AnimationProperties withInvertView(boolean invertView) { + this.invertView = invertView; + return this; + } + + public AnimationProperties copyOf(AnimationProperties properties) { + this.camYaw = properties.camYaw; + this.camPitch = properties.camPitch; + this.camRoll = properties.camRoll; + this.camY = properties.camY; + this.jumpModifier = properties.jumpModifier; + this.heightModifier = properties.heightModifier; + this.swapRotation = properties.swapRotation; + this.invertX = properties.invertX; + this.invertY = properties.invertY; + this.invertView = properties.invertView; + return this; + } + + public void setAdjacent(Integer up, Integer down, Integer left, Integer right, Integer forward, Integer back) { + this.up = up; + this.down = down; + this.left = left; + this.right = right; + this.forward = forward; + this.back = back; + } + + public void copyAdjacent(AnimationProperties properties) { + this.up = properties.up; + this.down = properties.down; + this.left = properties.left; + this.right = properties.right; + this.forward = properties.forward; + this.back = properties.back; + } + + public int getLayerLevel() { + return this.layerLevel; + } + + public int getAnimationIndex() { + return this.animationIndex; + } + + public float getCamYaw() { + return this.camYaw; + } + + public float getCamPitch() { + return this.camPitch; + } + + public float getCamRoll() { + return this.camRoll; + } + + public float getCamY() { + return this.camY; + } + + public float getJumpModifier() { + return this.jumpModifier; + } + + public float getHeightModifier() { + return this.heightModifier; + } + + public String getAnimationName() { + return this.animationName; + } + + public KeyframeAnimation getAnimation() { + return this.animation; + } + + public Integer getBack() { + return this.back; + } + + public Integer getForward() { + return this.forward; + } + + public Integer getRight() { + return this.right; + } + + public Integer getLeft() { + return this.left; + } + + public Integer getDown() { + return this.down; + } + + public Integer getUp() { + return this.up; + } + + public boolean isSwapRotation() { + return this.swapRotation; + } + + public boolean isInvertX() { + return this.invertX; + } + + public boolean isInvertY() { + return this.invertY; + } + + public boolean isInvertView() { + return this.invertView; + } +} diff --git a/src/main/java/top/r3944realms/eroticdungeongame/content/animation/ClientAnimationHandler.java b/src/main/java/top/r3944realms/eroticdungeongame/content/animation/ClientAnimationHandler.java new file mode 100644 index 00000000..880fdafb --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/content/animation/ClientAnimationHandler.java @@ -0,0 +1,25 @@ +package top.r3944realms.eroticdungeongame.content.animation; + +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; + +@OnlyIn(Dist.CLIENT) +public enum ClientAnimationHandler implements IAnimationHandler { + INSTANCE; + public static final Logger LOGGER = LoggerFactory.getLogger(ClientAnimationHandler.class); + public final Map SEAT_ANIMATIONS = new HashMap<>(); + + @Override + public void loadAllAnimations() { + loadSeatAnimations(); + } + + private void loadSeatAnimations() { + + } +} diff --git a/src/main/java/top/r3944realms/eroticdungeongame/content/animation/IAnimationHandler.java b/src/main/java/top/r3944realms/eroticdungeongame/content/animation/IAnimationHandler.java new file mode 100644 index 00000000..5452895a --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/content/animation/IAnimationHandler.java @@ -0,0 +1,7 @@ +package top.r3944realms.eroticdungeongame.content.animation; + +public interface IAnimationHandler { + default void loadAllAnimations() { + + } +} diff --git a/src/main/java/top/r3944realms/eroticdungeongame/content/animation/type/IAnimationType.java b/src/main/java/top/r3944realms/eroticdungeongame/content/animation/type/IAnimationType.java new file mode 100644 index 00000000..ba3da8a0 --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/content/animation/type/IAnimationType.java @@ -0,0 +1,7 @@ +package top.r3944realms.eroticdungeongame.content.animation.type; + +public interface IAnimationType { + String getTypeName(); + String getAnimationName(); + int getAnimalId(); +} diff --git a/src/main/java/top/r3944realms/eroticdungeongame/content/animation/type/SeatAnimationType.java b/src/main/java/top/r3944realms/eroticdungeongame/content/animation/type/SeatAnimationType.java new file mode 100644 index 00000000..f6082d18 --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/content/animation/type/SeatAnimationType.java @@ -0,0 +1,34 @@ +package top.r3944realms.eroticdungeongame.content.animation.type; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +public enum SeatAnimationType implements IAnimationType { + CUFF_BED(0, "cuff_bed", "edg_cuff_bed") + ; + private final String typeName; + private final String animationName; + private final int animationId; + SeatAnimationType(int animationId, String typeName, String animationName) { + this.typeName = typeName; + this.animationId = animationId; + this.animationName = animationName; + } + + @Override + public String getTypeName() { + return typeName; + } + + @Contract(pure = true) + @Override + public @NotNull String getAnimationName() { + return animationName; + } + + @Override + public int getAnimalId() { + return animationId; + } + +} diff --git a/src/main/java/top/r3944realms/eroticdungeongame/content/block/AbstractSeatBlock.java b/src/main/java/top/r3944realms/eroticdungeongame/content/block/AbstractSeatBlock.java new file mode 100644 index 00000000..a9712196 --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/content/block/AbstractSeatBlock.java @@ -0,0 +1,163 @@ +package top.r3944realms.eroticdungeongame.content.block; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.*; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.BooleanProperty; +import net.minecraft.world.level.block.state.properties.DirectionProperty; +import net.minecraft.world.level.material.PushReaction; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.VoxelShape; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.eroticdungeongame.content.block.blockentity.SeatBlockEntity; +import top.r3944realms.eroticdungeongame.content.util.FurnitureHelper; + +import java.util.Map; + +@SuppressWarnings({"deprecation", "unused"}) +public abstract class AbstractSeatBlock extends Block implements EntityBlock { + public static final DirectionProperty FACING = HorizontalDirectionalBlock.FACING; + public static final BooleanProperty OCCUPIED = BooleanProperty.create("occupied"); + public static final BooleanProperty FLIPPED = BooleanProperty.create("flipped"); + + /* renamed from: shapeMap */ + protected Map shapeMap; + + public AbstractSeatBlock(BlockBehaviour.Properties properties) { + super(properties); + registerDefaultState(defaultBlockState() + .setValue(FACING, Direction.NORTH) + .setValue(OCCUPIED, false) + .setValue(FLIPPED, false)); + } + + /** + * 抽象方法:子类必须实现来设置碰撞箱形状 + */ + public abstract void setupShape(); + + @Override + public BlockEntity newBlockEntity(@NotNull BlockPos blockPos, @NotNull BlockState blockState) { + return new SeatBlockEntity(blockPos, blockState); + } + + @Override + public @NotNull BlockState rotate(@NotNull BlockState blockState, @NotNull Rotation rotation) { + return blockState; + } + + @Override + public @NotNull BlockState mirror(@NotNull BlockState blockState, @NotNull Mirror mirror) { + return blockState; + } + + @Override + public @NotNull VoxelShape getShape(@NotNull BlockState blockState, @NotNull BlockGetter blockGetter, + @NotNull BlockPos blockPos, @NotNull CollisionContext collisionContext) { + return this.shapeMap.get(blockState.getValue(FACING)); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + builder.add(FACING, OCCUPIED, FLIPPED); + } + + @Override + public BlockState getStateForPlacement(BlockPlaceContext context) { + return defaultBlockState() + .setValue(FACING, context.getHorizontalDirection().getOpposite()) + .setValue(OCCUPIED, false) + .setValue(FLIPPED, false); + } + + @Override + public PushReaction getPistonPushReaction(@NotNull BlockState blockState) { + return PushReaction.DESTROY; + } + + @Override + public boolean useShapeForLightOcclusion(@NotNull BlockState blockState) { + return true; + } + + @Override + public int getLightEmission(BlockState state, BlockGetter level, BlockPos pos) { + return state.getValue(OCCUPIED) ? 15 : 0; + } + + + @Override + public boolean isSignalSource(@NotNull BlockState blockState) { + return true; + } + + @Override + public int getSignal(@NotNull BlockState blockState, @NotNull BlockGetter blockGetter, + @NotNull BlockPos blockPos, @NotNull Direction direction) { + return blockState.getValue(OCCUPIED) ? 15 : 0; + } + + @Override + @NotNull + public InteractionResult use(@NotNull BlockState blockState, @NotNull Level level, + @NotNull BlockPos blockPos, @NotNull Player player, + @NotNull InteractionHand hand, @NotNull BlockHitResult hitResult) { + // 客户端或副手直接返回 + if (level.isClientSide || hand == InteractionHand.OFF_HAND) { + return InteractionResult.SUCCESS; + } + + SeatBlockEntity deviceEntity = FurnitureHelper.getSeatBlockEntity(level, blockPos); + if (deviceEntity != null) { + if (blockState.getValue(OCCUPIED)) { + // 设备已被占用 - 释放玩家 + if (FurnitureHelper.isRestraintDevice(blockState.getBlock())) { + Player boundPlayer = level.getPlayerByUUID(deviceEntity.getBoundPlayerUUID()); + if (boundPlayer != null) { + deviceEntity.flipSeatDirection(level, blockPos, blockState); + return InteractionResult.SUCCESS; + } + } + } else { + // 设备空闲 - 绑定玩家 + if (!player.isShiftKeyDown()) { + deviceEntity.bindPlayerToSeat(level, blockPos, blockState, player); + return InteractionResult.SUCCESS; + } + } + } + return InteractionResult.SUCCESS; + } + + @Override + public void onRemove(@NotNull BlockState blockState, Level level, @NotNull BlockPos blockPos, + @NotNull BlockState newState, boolean moved) { + if (!level.isClientSide && blockState.getBlock() != newState.getBlock()) { + SeatBlockEntity deviceEntity = FurnitureHelper.getSeatBlockEntity(level, blockPos); + if (deviceEntity != null && blockState.getValue(OCCUPIED)) { + Player boundPlayer = level.getPlayerByUUID(deviceEntity.getBoundPlayerUUID()); + if (boundPlayer != null) { + FurnitureHelper.releasePlayerFromDevice(boundPlayer); + } + } + } + super.onRemove(blockState, level, blockPos, newState, moved); + } + + @Override + @NotNull + public RenderShape getRenderShape(@NotNull BlockState blockState) { + return RenderShape.MODEL; + } +} diff --git a/src/main/java/top/r3944realms/eroticdungeongame/content/block/AbstractTwoPartSeatBlock.java b/src/main/java/top/r3944realms/eroticdungeongame/content/block/AbstractTwoPartSeatBlock.java new file mode 100644 index 00000000..742406c1 --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/content/block/AbstractTwoPartSeatBlock.java @@ -0,0 +1,96 @@ +package top.r3944realms.eroticdungeongame.content.block; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.EnumProperty; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.VoxelShape; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import top.r3944realms.eroticdungeongame.content.block.blockentity.SeatBlockEntity; +import top.r3944realms.eroticdungeongame.content.block.part.SeatPart; + +import java.util.Map; + +@SuppressWarnings("unused") +public abstract class AbstractTwoPartSeatBlock extends AbstractSeatBlock { + public static final EnumProperty PART = EnumProperty.create("part", SeatPart.class); + protected Map> shapeMap; + public AbstractTwoPartSeatBlock(Properties properties) { + super(properties); + registerDefaultState(getStateDefinition().any().setValue(PART, SeatPart.FOOT)); + } + + @Override + public BlockEntity newBlockEntity(@NotNull BlockPos blockPos, @NotNull BlockState blockState) { + if(blockState.getValue(PART) == SeatPart.FOOT) { + return new SeatBlockEntity(blockPos, blockState); + } + return null; + } + + @Override + public @NotNull VoxelShape getShape(@NotNull BlockState blockState, @NotNull BlockGetter blockGetter, @NotNull BlockPos blockPos, @NotNull CollisionContext collisionContext) { + return shapeMap.get(blockState.getValue(PART)).get(blockState.getValue(FACING)); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.@NotNull Builder builder) { + builder.add(PART, OCCUPIED, FACING, FLIPPED); + } + + @Override + public BlockState getStateForPlacement(@NotNull BlockPlaceContext context) { + Direction direction = context.getClickedFace(); + BlockPos blockPos = context.getClickedPos(); + Level level = context.getLevel(); + if (level.isInWorldBounds(blockPos) && level.getBlockState(blockPos).canBeReplaced(context)) { + return defaultBlockState() + .setValue(FACING, direction) + .setValue(PART, SeatPart.FOOT) + .setValue(OCCUPIED, Boolean.FALSE) + .setValue(FLIPPED, Boolean.FALSE); + } + return null; + } + + @Override + public void setPlacedBy(@NotNull Level level, @NotNull BlockPos pos, @NotNull BlockState state, @Nullable LivingEntity placer, @NotNull ItemStack stack) { + level.setBlock(pos.relative(state.getValue(FACING)), state.setValue(PART, SeatPart.HEAD), Block.UPDATE_ALL); + } + + @Override + public @NotNull InteractionResult use(@NotNull BlockState blockState, @NotNull Level level, @NotNull BlockPos blockPos, @NotNull Player player, @NotNull InteractionHand hand, @NotNull BlockHitResult hitResult) { + BlockPos blockPos1 = blockPos; + if(SeatPart.HEAD == blockState.getValue(PART)) { + blockPos1 = blockPos.relative(blockState.getValue(FACING).getOpposite()); + } + return super.use(blockState, level, blockPos1, player, hand, hitResult); + } + + @Override + public void onRemove(@NotNull BlockState blockState, Level level, @NotNull BlockPos blockPos, @NotNull BlockState newState, boolean moved) { + if (blockState.getBlock() != newState.getBlock()) { + Direction direction = blockState.getValue(FACING); + BlockPos blockPos1 = blockState.getValue(PART) == SeatPart.FOOT ? blockPos.relative(direction) : blockPos.relative(direction.getOpposite()); + BlockState blockState1 = level.getBlockState(blockPos1); + if (blockState1.getBlock() == this && blockState1.getValue(PART) != blockState.getValue(PART)) { + level.removeBlock(blockPos1, false); + } + } + super.onRemove(blockState, level, blockPos, newState, moved); + } +} diff --git a/src/main/java/top/r3944realms/eroticdungeongame/content/block/FurnitureType.java b/src/main/java/top/r3944realms/eroticdungeongame/content/block/FurnitureType.java new file mode 100644 index 00000000..fe1df2a7 --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/content/block/FurnitureType.java @@ -0,0 +1,8 @@ +package top.r3944realms.eroticdungeongame.content.block; + +public enum FurnitureType { + NORMAL, + HORIZONTAL_DOUBLE, + HORIZONTAL_TRIPLE, + VERTICAL_DOUBLE +} diff --git a/src/main/java/top/r3944realms/eroticdungeongame/content/block/HorizontalDoubleSeatBlock.java b/src/main/java/top/r3944realms/eroticdungeongame/content/block/HorizontalDoubleSeatBlock.java new file mode 100644 index 00000000..3c8aa8b6 --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/content/block/HorizontalDoubleSeatBlock.java @@ -0,0 +1,46 @@ +package top.r3944realms.eroticdungeongame.content.block; + +import net.minecraft.core.Direction; +import net.minecraft.world.level.block.SoundType; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.phys.shapes.VoxelShape; +import top.r3944realms.eroticdungeongame.content.block.part.SeatPart; +import top.r3944realms.lib39.util.shape.ShapeUtil; + +import java.util.EnumMap; +import java.util.Map; + +public class HorizontalDoubleSeatBlock extends AbstractTwoPartSeatBlock { + private static final BlockBehaviour.Properties PROPERTIES = BlockBehaviour.Properties.of() + .strength(2.0f) + .explosionResistance(3.0f) + .sound(SoundType.WOOD) + .noOcclusion(); + public HorizontalDoubleSeatBlock() { + super(PROPERTIES); + setupShape(); + } + + @Override + public void setupShape() { + // 第一部分:主体平台 + 前侧两个支柱 + VoxelShape group1 = ShapeUtil.builder() + .addPixelBox(0.0, 3.0, 0.0, 16.0, 9.0, 16.0) // 主体平台 + .addPixelBox(13.0, 0.0, 0.0, 16.0, 3.0, 3.0) // 前右支柱 + .addPixelBox(0.0, 0.0, 0.0, 3.0, 3.0, 3.0) // 前左支柱 + .build(); + + // 第二部分:主体平台 + 后侧两个支柱 + VoxelShape group2 = ShapeUtil.builder() + .addPixelBox(0.0, 3.0, 0.0, 16.0, 9.0, 16.0) // 主体平台 + .addPixelBox(0.0, 0.0, 13.0, 3.0, 3.0, 16.0) // 后左支柱 + .addPixelBox(13.0, 0.0, 13.0, 16.0, 3.0, 16.0)// 后右支柱 + .build(); + + // 创建双部分方块的形状映射 + EnumMap> shapeMap = new EnumMap<>(SeatPart.class); + shapeMap.put(SeatPart.HEAD, ShapeUtil.createUniformDirectionMap(group1)); + shapeMap.put(SeatPart.FOOT, ShapeUtil.createUniformDirectionMap(group2)); + this.shapeMap = shapeMap; + } +} diff --git a/src/main/java/top/r3944realms/eroticdungeongame/content/block/blockentity/SeatBlockEntity.java b/src/main/java/top/r3944realms/eroticdungeongame/content/block/blockentity/SeatBlockEntity.java new file mode 100644 index 00000000..3b82c36d --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/content/block/blockentity/SeatBlockEntity.java @@ -0,0 +1,203 @@ +package top.r3944realms.eroticdungeongame.content.block.blockentity; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.BooleanProperty; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.eroticdungeongame.content.block.AbstractSeatBlock; +import top.r3944realms.eroticdungeongame.content.device.SeatType; +import top.r3944realms.eroticdungeongame.content.entity.SeatEntity; +import top.r3944realms.eroticdungeongame.content.register.EDGBlockEntities; +import top.r3944realms.eroticdungeongame.content.util.FurnitureHelper; + +import java.util.UUID; + +/** + * 地牢座椅方块实体 - 处理玩家坐在束缚设备上的逻辑 + */ +@SuppressWarnings("unused") +public class SeatBlockEntity extends BlockEntity { + + private int seatEntityId; + private UUID boundPlayerUUID; + + public SeatBlockEntity(BlockPos blockPos, BlockState blockState) { + super(EDGBlockEntities.SEAT_BLOCK_ENTITY.get(), blockPos, blockState); + } + + // Getter/Setter 方法使用有意义的名称 + public UUID getBoundPlayerUUID() { + return this.boundPlayerUUID; + } + + public void setBoundPlayerUUID(UUID uuid) { + this.boundPlayerUUID = uuid; + setChanged(); + } + + public int getSeatEntityId() { + return this.seatEntityId; + } + + public void setSeatEntityId(int entityId) { + this.seatEntityId = entityId; + setChanged(); + } + + /** + * 绑定玩家到座椅 + */ + public void bindPlayerToSeat(Level level, BlockPos blockPos, BlockState blockState, Player player) { + SeatType furnitureType = FurnitureHelper.getSeatType(blockState.getBlock()); + if (furnitureType == null) { + return; + } + + Direction direction = blockState.getValue(AbstractSeatBlock.FACING); + if (furnitureType.isOppositeDirection()) { + direction = direction.getOpposite(); + } + + // 创建座椅实体 + SeatEntity seatEntity = new SeatEntity(furnitureType.getEntityType(), level); + double[] seatPosition = calculateSeatPosition(blockPos, direction, furnitureType); + + seatEntity.setPos(seatPosition[0], seatPosition[1], seatPosition[2]); + + // 设置座椅和玩家的朝向 + float yaw = calculateSeatYaw(direction); + seatEntity.setYRot(yaw); + player.setYRot(yaw); + + level.addFreshEntity(seatEntity); + seatEntity.setLinkedBlockPos(blockPos); + + // 更新方块实体数据 + setSeatEntityId(seatEntity.getId()); + setBoundPlayerUUID(player.getUUID()); + + // 更新方块状态 + updateBlockState(level, blockPos, blockState, AbstractSeatBlock.OCCUPIED, true); + updateBlockState(level, blockPos, blockState, AbstractSeatBlock.FLIPPED, false); + + // 让玩家乘坐座椅 + player.startRiding(seatEntity, true); + } + + /** + * 翻转座椅方向 + */ + public void flipSeatDirection(Level level, BlockPos blockPos, @NotNull BlockState blockState) { + Direction direction = blockState.getValue(AbstractSeatBlock.FACING); + boolean isFlipped = blockState.getValue(AbstractSeatBlock.FLIPPED); + + float yaw = calculateSeatYaw(isFlipped ? direction : direction.getOpposite()); + + // 更新实体朝向 + FurnitureHelper.getEntityById(level, getSeatEntityId()) + .filter(e -> e instanceof SeatEntity) + .ifPresent(seat -> seat.setYRot(yaw)); + + // 翻转方块状态 + updateBlockState(level, blockPos, blockState, AbstractSeatBlock.FLIPPED, !isFlipped); + } + + /** + * 释放玩家 + */ + public void releasePlayer(@NotNull Level level, BlockPos blockPos) { + BlockState blockState = level.getBlockState(blockPos); + updateBlockState(level, blockPos, blockState, AbstractSeatBlock.OCCUPIED, false); + updateBlockState(level, blockPos, blockState, AbstractSeatBlock.FLIPPED, false); + + // 清理数据 + this.seatEntityId = 0; + this.boundPlayerUUID = null; + setChanged(); + } + + /** + * 计算座椅位置 + */ + @Contract("_, _, _ -> new") + private double @NotNull [] calculateSeatPosition(@NotNull BlockPos blockPos, @NotNull Direction direction, @NotNull SeatType furnitureType) { + double x = furnitureType.getOffsetX(blockPos.getX()); + double y = furnitureType.getOffsetY(blockPos.getY()); + double z = furnitureType.getOffsetZ(blockPos.getZ()); + double offset = furnitureType.getGeneralOffset(); + + // 根据方向调整位置 + switch (direction) { + case NORTH -> x -= offset; + case SOUTH -> x += offset; + case WEST -> z -= offset; + case EAST -> z += offset; + } + + return new double[]{x, y, z}; + } + + /** + * 计算座椅朝向 + */ + @Contract(pure = true) + private float calculateSeatYaw(@NotNull Direction direction) { + return switch (direction) { + case NORTH -> 180.0f; + case WEST -> 90.0f; + case EAST -> 270.0f; + default -> 0.0f; + }; + } + + /** + * 更新多方块结构的状态 + */ + private void updateBlockState(Level level, BlockPos blockPos, @NotNull BlockState blockState, + BooleanProperty property, boolean value) { + SeatType seatType = FurnitureHelper.getSeatType(blockState.getBlock()); + if (seatType == null) return; + + // 根据家具类型更新相关的方块 + switch (seatType.getFurnitureType()) { + case NORMAL -> + FurnitureHelper.setBlockStates(level, property, value, blockPos); + case HORIZONTAL_DOUBLE -> + FurnitureHelper.setBlockStates(level, property, value, blockPos, + blockPos.relative(blockState.getValue(AbstractSeatBlock.FACING))); + case HORIZONTAL_TRIPLE -> + FurnitureHelper.setBlockStates(level, property, value, blockPos, + blockPos.relative(blockState.getValue(AbstractSeatBlock.FACING)), + blockPos.relative(blockState.getValue(AbstractSeatBlock.FACING).getClockWise())); + case VERTICAL_DOUBLE -> + FurnitureHelper.setBlockStates(level, property, value, blockPos, + blockPos.above()); + } + } + + @Override + public void saveAdditional(@NotNull CompoundTag compoundTag) { + super.saveAdditional(compoundTag); + if (this.boundPlayerUUID != null) { + compoundTag.putInt("SeatEntityID", this.seatEntityId); + compoundTag.putUUID("PlayerUUID", this.boundPlayerUUID); + } + } + + @Override + public void load(@NotNull CompoundTag compoundTag) { + super.load(compoundTag); + if (compoundTag.contains("SeatEntityID")) { + this.seatEntityId = compoundTag.getInt("SeatEntityID"); + } + if (compoundTag.contains("PlayerUUID")) { + this.boundPlayerUUID = compoundTag.getUUID("PlayerUUID"); + } + } +} \ No newline at end of file diff --git a/src/main/java/top/r3944realms/eroticdungeongame/content/block/part/SeatPart.java b/src/main/java/top/r3944realms/eroticdungeongame/content/block/part/SeatPart.java new file mode 100644 index 00000000..c68b10c8 --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/content/block/part/SeatPart.java @@ -0,0 +1,19 @@ +package top.r3944realms.eroticdungeongame.content.block.part; + +import net.minecraft.util.StringRepresentable; +import org.jetbrains.annotations.NotNull; + +public enum SeatPart implements StringRepresentable { + HEAD("head"), + FOOT("foot"), + ; + private final String name; + SeatPart(String name) { + this.name = name; + } + + @Override + public @NotNull String getSerializedName() { + return this.name; + } +} diff --git a/src/main/java/top/r3944realms/eroticdungeongame/content/device/SeatSessionManager.java b/src/main/java/top/r3944realms/eroticdungeongame/content/device/SeatSessionManager.java new file mode 100644 index 00000000..1f4ba471 --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/content/device/SeatSessionManager.java @@ -0,0 +1,4 @@ +package top.r3944realms.eroticdungeongame.content.device; + +public class SeatSessionManager { +} diff --git a/src/main/java/top/r3944realms/eroticdungeongame/content/device/SeatType.java b/src/main/java/top/r3944realms/eroticdungeongame/content/device/SeatType.java new file mode 100644 index 00000000..01e9cdcb --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/content/device/SeatType.java @@ -0,0 +1,81 @@ +package top.r3944realms.eroticdungeongame.content.device; + +import net.minecraft.world.entity.EntityType; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.eroticdungeongame.content.animation.type.SeatAnimationType; +import top.r3944realms.eroticdungeongame.content.block.FurnitureType; +import top.r3944realms.eroticdungeongame.content.entity.SeatEntity; +import top.r3944realms.eroticdungeongame.content.register.EDGEntities; + +@SuppressWarnings("unused") +public enum SeatType { + CUFF_BED(SeatAnimationType.CUFF_BED, FurnitureType.HORIZONTAL_DOUBLE, "cuff_bed", 0.5d, 0.35d, 0.5d, 0.3d, true), + ; + private final SeatAnimationType animation; + private final String blockName; + private final double offsetX; + private final double offsetY; + private final double offsetZ; + private final double generalOffset; + + private static final SeatTypeRegistry REGISTRY = new SeatTypeRegistry(); + + static { + REGISTRY.register(CUFF_BED, EDGEntities.CUFF_BED_SEAT); + } + public FurnitureType getFurnitureType() { + return furnitureType; + } + + public boolean isOppositeDirection() { + return isOppositeDirection; + } + + public double getGeneralOffset() { + return generalOffset; + } + + public double getOffsetZ(double partialTick) { + return offsetZ + partialTick; + } + + public double getOffsetY(double partialTick) { + return offsetY + partialTick; + } + + public double getOffsetX(double partialTick) { + return offsetX + partialTick; + } + + @Contract(pure = true) + public @NotNull String getBlockName() { + return blockName + "_seat"; + } + + public SeatAnimationType getAnimation() { + return animation; + } + /** + * 获取座位对应的实体类型 + */ + public @NotNull EntityType getEntityType() { + return REGISTRY.getEntityType(this); + } + + + private final boolean isOppositeDirection; + private final FurnitureType furnitureType; + + SeatType(SeatAnimationType animation, FurnitureType furnitureType, String blockName, double offsetX, double offsetY, double offsetZ, double generalOffset, boolean isOppositeDirection) { + + this.animation = animation; + this.blockName = blockName; + this.offsetX = offsetX; + this.offsetY = offsetY; + this.offsetZ = offsetZ; + this.generalOffset = generalOffset; + this.isOppositeDirection = isOppositeDirection; + this.furnitureType = furnitureType; + } +} diff --git a/src/main/java/top/r3944realms/eroticdungeongame/content/device/SeatTypeRegistry.java b/src/main/java/top/r3944realms/eroticdungeongame/content/device/SeatTypeRegistry.java new file mode 100644 index 00000000..f3ea7859 --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/content/device/SeatTypeRegistry.java @@ -0,0 +1,64 @@ +package top.r3944realms.eroticdungeongame.content.device; + +import net.minecraft.world.entity.EntityType; +import net.minecraftforge.registries.RegistryObject; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.eroticdungeongame.content.entity.SeatEntity; + +import java.util.EnumMap; +import java.util.Map; + +/** + * 座位类型映射管理器 + */ +@SuppressWarnings("unused") +public class SeatTypeRegistry { + private final Map>> entityTypeMap = new EnumMap<>(SeatType.class); + private final Map> cachedEntityTypes = new EnumMap<>(SeatType.class); + + /** + * 注册座位类型与实体类型的映射 + */ + public void register(@NotNull SeatType seatType, @NotNull RegistryObject> entityType) { + entityTypeMap.put(seatType, entityType); + } + + /** + * 获取座位类型对应的实体类型 + */ + @NotNull + public EntityType getEntityType(@NotNull SeatType seatType) { + // 使用缓存避免重复获取 + return cachedEntityTypes.computeIfAbsent(seatType, st -> { + RegistryObject> registryObject = entityTypeMap.get(st); + if (registryObject == null) { + throw new IllegalStateException("No entity type registered for seat type: " + st.name()); + } + if (!registryObject.isPresent()) { + throw new IllegalStateException("Entity type not yet registered for seat type: " + st.name()); + } + return registryObject.get(); + }); + } + + /** + * 检查座位类型是否已注册实体类型 + */ + public boolean isRegistered(@NotNull SeatType seatType) { + return entityTypeMap.containsKey(seatType) && entityTypeMap.get(seatType).isPresent(); + } + + /** + * 获取所有已注册的座位类型 + */ + public @NotNull Iterable getRegisteredSeatTypes() { + return entityTypeMap.keySet(); + } + + /** + * 清空缓存(用于开发重载) + */ + public void clearCache() { + cachedEntityTypes.clear(); + } +} diff --git a/src/main/java/top/r3944realms/eroticdungeongame/content/entity/SeatEntity.java b/src/main/java/top/r3944realms/eroticdungeongame/content/entity/SeatEntity.java new file mode 100644 index 00000000..53078682 --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/content/entity/SeatEntity.java @@ -0,0 +1,195 @@ +package top.r3944realms.eroticdungeongame.content.entity; + + +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientGamePacketListener; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.network.NetworkHooks; +import org.jetbrains.annotations.NotNull; + +/** + * 座椅实体 - 用于处理玩家坐在束缚设备上的逻辑 + */ +public class SeatEntity extends Entity { + + private BlockPos linkedBlockPos; + private boolean isRemoving; + + public SeatEntity(EntityType entityType, Level level) { + super(entityType, level); + this.isRemoving = false; + } + + @Override + public void positionRider(@NotNull Entity passenger, @NotNull MoveFunction callback) { + super.positionRider(passenger); + + // 客户端不需要处理 + if (this.level().isClientSide()) { + return; + } + + updateLinkedBlock(); + } + + @Override + public void removePassenger(@NotNull Entity passenger) { + super.removePassenger(passenger); + + // 只在服务端处理,且确保没有其他乘客 + if (this.level().isClientSide() || !getPassengers().isEmpty()) { + return; + } + + updateLinkedBlock(); + + // 如果是生物实体,设置其位置 + if (passenger instanceof LivingEntity livingEntity) { + Vec3 dismountPosition = getDismountLocationForPassenger(livingEntity); + passenger.setPos(dismountPosition.x(), dismountPosition.y(), dismountPosition.z()); + } + } + + @Override + public void onPassengerTurned(@NotNull Entity passenger) { + super.onPassengerTurned(passenger); + +// // 如果不是笼子类型的座椅,限制乘客的旋转 (未做) +// if (!SeatType.CAGE.equals(FurnitureHelper.get(this)) && +// passenger instanceof LivingEntity livingEntity) { +// +// limitPassengerRotation(livingEntity); +// } + } + + /** + * 限制乘客的旋转角度 + */ + private void limitPassengerRotation(LivingEntity passenger) { + passenger.setYBodyRot(getYRot()); + + float deltaYaw = Mth.wrapDegrees(passenger.getYRot() - getYRot()); + float clampedYaw = Mth.clamp(deltaYaw, -90.0f, 90.0f); + + passenger.yRotO += clampedYaw - deltaYaw; + passenger.setYRot(passenger.getYRot() + clampedYaw - deltaYaw); + passenger.setYHeadRot(passenger.getYRot()); + } + + @Override + public boolean shouldRiderSit() { + return false; + } + + @Override + public boolean isPickable() { + return false; + } + + /** + * 更新链接的方块状态 + */ + private void updateLinkedBlock() { + if (this.linkedBlockPos != null) { + this.level().blockUpdated(this.linkedBlockPos, + this.level().getBlockState(this.linkedBlockPos).getBlock()); + } + } + + @Override + @NotNull + public Vec3 getDismountLocationForPassenger(@NotNull LivingEntity passenger) { + BlockPos blockPos = getLinkedBlockPos().above(); + + // 在周围寻找合适的下马位置 + for (int x = -1; x <= 1; x++) { + for (int z = -1; z <= 1; z++) { + if (x != 0 || z != 0) { + BlockPos checkPos = blockPos.offset(x, 0, z); + if (this.level().isEmptyBlock(checkPos) && + this.level().isEmptyBlock(checkPos.above())) { + return new Vec3( + checkPos.getX() + 0.5d, + checkPos.getY(), + checkPos.getZ() + 0.5d + ); + } + } + } + } + + // 如果没有找到合适位置,返回当前位置 + return position(); + } + + @Override + @NotNull + public Packet getAddEntityPacket() { + return NetworkHooks.getEntitySpawningPacket(this); + } + + @Override + public boolean isAttackable() { + return false; + } + + @Override + protected void defineSynchedData() { + // 座椅实体不需要同步数据 + } + + @Override + public void addAdditionalSaveData(@NotNull CompoundTag compound) { + if (this.linkedBlockPos != null) { + compound.putInt("LinkedBlockX", this.linkedBlockPos.getX()); + compound.putInt("LinkedBlockY", this.linkedBlockPos.getY()); + compound.putInt("LinkedBlockZ", this.linkedBlockPos.getZ()); + } + } + + @Override + public void readAdditionalSaveData(@NotNull CompoundTag compound) { + this.linkedBlockPos = new BlockPos( + compound.getInt("LinkedBlockX"), + compound.getInt("LinkedBlockY"), + compound.getInt("LinkedBlockZ") + ); + } + + // Getter 和 Setter 方法 + + public BlockPos getLinkedBlockPos() { + return this.linkedBlockPos; + } + + public void setLinkedBlockPos(BlockPos blockPos) { + this.linkedBlockPos = blockPos; + } + + public boolean isActive() { + return !this.isRemoving; + } + + public void setRemoving(boolean removing) { + this.isRemoving = removing; + } + + /** + * 安全地移除座椅实体 + */ + public void safeRemove() { + if (!this.level().isClientSide()) { + // 先释放所有乘客 + getPassengers().forEach(this::removePassenger); + // 然后移除实体 + discard(); + } + } +} diff --git a/src/main/java/top/r3944realms/eroticdungeongame/content/register/EDGBlockEntities.java b/src/main/java/top/r3944realms/eroticdungeongame/content/register/EDGBlockEntities.java new file mode 100644 index 00000000..3f7958de --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/content/register/EDGBlockEntities.java @@ -0,0 +1,23 @@ +package top.r3944realms.eroticdungeongame.content.register; + +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegistryObject; +import top.r3944realms.eroticdungeongame.EroticDungeon; +import top.r3944realms.eroticdungeongame.content.block.blockentity.SeatBlockEntity; + +public class EDGBlockEntities { + public static final DeferredRegister> BLOCK_ENTITIES = DeferredRegister.create(ForgeRegistries.BLOCK_ENTITY_TYPES, EroticDungeon.MOD_ID); + @SuppressWarnings("DataFlowIssue") + public static final RegistryObject> SEAT_BLOCK_ENTITY = BLOCK_ENTITIES.register("seat_block_entity", + () -> BlockEntityType.Builder + .of(SeatBlockEntity::new, EDGBlocks.CUFF_BED.get()) + .build(null) + ); + public static void register(IEventBus eventBus) { + BLOCK_ENTITIES.register(eventBus); + } + +} diff --git a/src/main/java/top/r3944realms/eroticdungeongame/content/register/EDGBlocks.java b/src/main/java/top/r3944realms/eroticdungeongame/content/register/EDGBlocks.java new file mode 100644 index 00000000..fc82002a --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/content/register/EDGBlocks.java @@ -0,0 +1,30 @@ +package top.r3944realms.eroticdungeongame.content.register; + +import net.minecraft.world.level.block.Block; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegistryObject; +import top.r3944realms.eroticdungeongame.EroticDungeon; +import top.r3944realms.eroticdungeongame.content.block.HorizontalDoubleSeatBlock; +import top.r3944realms.lib39.util.block.BlockRegistryBuilder; + +import java.util.ArrayList; +import java.util.List; + +public class EDGBlocks { + public static final DeferredRegister BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, EroticDungeon.MOD_ID); + public static final List> SEAT_BLOCKS = new ArrayList<>(); + public static final RegistryObject CUFF_BED = + BlockRegistryBuilder + .create() + .withName("cuff_bed") + .registerBlock(BLOCKS, HorizontalDoubleSeatBlock::new) + .build(); + static { + SEAT_BLOCKS.add(CUFF_BED); + } + public static void register(IEventBus eventBus) { + BLOCKS.register(eventBus); + } +} diff --git a/src/main/java/top/r3944realms/eroticdungeongame/content/register/EDGCreativeTabs.java b/src/main/java/top/r3944realms/eroticdungeongame/content/register/EDGCreativeTabs.java new file mode 100644 index 00000000..7e73d621 --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/content/register/EDGCreativeTabs.java @@ -0,0 +1,25 @@ +package top.r3944realms.eroticdungeongame.content.register; + +import net.minecraft.core.registries.Registries; +import net.minecraft.network.chat.Component; +import net.minecraft.world.item.CreativeModeTab; +import net.minecraft.world.level.block.Blocks; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.RegistryObject; +import top.r3944realms.eroticdungeongame.EroticDungeon; + +public class EDGCreativeTabs { + private static final DeferredRegister CREATIVE_TABS = DeferredRegister.create(Registries.CREATIVE_MODE_TAB, EroticDungeon.MOD_ID); + + private static final RegistryObject MAIN = CREATIVE_TABS.register("main", () -> CreativeModeTab.builder() + .title(Component.literal("")) + .icon(() -> Blocks.BARRIER.asItem().getDefaultInstance()) + .displayItems((itemDisplayParameters, output) -> {}) + .build() + ); + + public static void register(IEventBus eventBus) { + CREATIVE_TABS.register(eventBus); + } +} diff --git a/src/main/java/top/r3944realms/eroticdungeongame/content/register/EDGEntities.java b/src/main/java/top/r3944realms/eroticdungeongame/content/register/EDGEntities.java new file mode 100644 index 00000000..57b34a19 --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/content/register/EDGEntities.java @@ -0,0 +1,29 @@ +package top.r3944realms.eroticdungeongame.content.register; + +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.MobCategory; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegistryObject; +import top.r3944realms.eroticdungeongame.EroticDungeon; +import top.r3944realms.eroticdungeongame.content.device.SeatType; +import top.r3944realms.eroticdungeongame.content.entity.SeatEntity; + +public class EDGEntities { + public static final DeferredRegister> ENTITIES = DeferredRegister.create(ForgeRegistries.ENTITY_TYPES, EroticDungeon.MOD_ID); + public static final RegistryObject> CUFF_BED_SEAT = registerSeat(SeatType.CUFF_BED.getBlockName(), SeatEntity::new); + + private static RegistryObject> registerSeat(String str, EntityType.EntityFactory entityFactory) { + return ENTITIES.register(str, () -> EntityType.Builder + .of(entityFactory, MobCategory.MISC) + .sized(0.0f, 0.0f) + .updateInterval(4) + .build(EroticDungeon.rl(str).toString())); + } + public static void register(IEventBus eventBus) { + ENTITIES.register(eventBus); + } + +} diff --git a/src/main/java/top/r3944realms/eroticdungeongame/content/register/EDGItems.java b/src/main/java/top/r3944realms/eroticdungeongame/content/register/EDGItems.java new file mode 100644 index 00000000..b7259a0e --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/content/register/EDGItems.java @@ -0,0 +1,11 @@ +package top.r3944realms.eroticdungeongame.content.register; + +import net.minecraft.world.item.Item; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import top.r3944realms.eroticdungeongame.EroticDungeon; + +public class EDGItems { + public static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, EroticDungeon.MOD_ID); + +} diff --git a/src/main/java/top/r3944realms/eroticdungeongame/content/util/FurnitureHelper.java b/src/main/java/top/r3944realms/eroticdungeongame/content/util/FurnitureHelper.java new file mode 100644 index 00000000..f5b5475d --- /dev/null +++ b/src/main/java/top/r3944realms/eroticdungeongame/content/util/FurnitureHelper.java @@ -0,0 +1,111 @@ +package top.r3944realms.eroticdungeongame.content.util; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.BooleanProperty; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import top.r3944realms.eroticdungeongame.EroticDungeon; +import top.r3944realms.eroticdungeongame.content.block.blockentity.SeatBlockEntity; +import top.r3944realms.eroticdungeongame.content.device.SeatType; +import top.r3944realms.eroticdungeongame.content.entity.SeatEntity; + +import java.util.*; + +public class FurnitureHelper { + private FurnitureHelper() { + } + + public static List RESTRAINT_FURNACES = new ArrayList<>(); + public static @Nullable String getEntityDeviceId(@NotNull Entity entity) { + // "entity.eroticdungeongame.wall_cuff -> 8 + modid -> wall_cuff" + return entity.getEncodeId() == null ? null : entity.getEncodeId().substring(8 + EroticDungeon.MOD_ID.length()); + } + + public static Optional getEntityById(@NotNull Level level, int entityId) { + return Optional.ofNullable(level.getEntity(entityId)); + } + + public static @Nullable SeatBlockEntity getSeatBlockEntity(@NotNull Level level, BlockPos blockPos) { + BlockEntity blockEntity = level.getBlockEntity(blockPos); + if (blockEntity instanceof SeatBlockEntity seatBlockEntity) { + return seatBlockEntity; + } + return null; + } + + public static boolean isRestraintDevice(Block block) { + return RESTRAINT_FURNACES.contains(block); + } + + public static void setBlockStates(Level level, BooleanProperty property, boolean value, BlockPos @NotNull ... blockPositions) { + for (BlockPos blockPos : blockPositions) { + BlockState blockState = level.getBlockState(blockPos); + if (blockState.hasProperty(property)) { + level.setBlock(blockPos, blockState.setValue(property, value), Block.UPDATE_ALL); + } + } + } + + public static SeatType getSeatType(@NotNull Block block) { + String registryName = block.getName().toString(); + return Arrays.stream(SeatType.values()) + .filter(seatType -> seatType.name().equals(registryName)) + .findFirst() + .orElse(null); + } + + public static boolean isPlayerBound(UUID playerUuid) { + return DeviceManager.getInstance().entrySet().stream() + .anyMatch(entry -> entry.getValue().getBoundPlayerUuid().equals(playerUuid)); + } + public static void releasePlayerFromDevice(Player player) { + SeatBlockEntity deviceBlockEntity; + + // 检查玩家是否骑乘在实体上 + if (player.isPassenger()) { + Entity vehicle = player.getVehicle(); + + // 检查骑乘的实体是否是设备实体 + if (vehicle instanceof SeatEntity seatEntity) { + + // 检查设备是否释放 + if (seatEntity.isActive()) { + // 标记设备为已释放状态 + seatEntity.setRemoving(true); + + // 移除设备实体 + seatEntity.remove(Entity.RemovalReason.DISCARDED); + + // 获取对应的方块实体并处理释放逻辑 + if (seatEntity.getLinkedBlockPos() == null || + (deviceBlockEntity = getSeatBlockEntity(player.level(), seatEntity.getLinkedBlockPos())) == null) { + return; + } + + // 通知方块实体玩家已释放 + deviceBlockEntity.releasePlayer(player.level(), deviceBlockEntity.getBlockPos()); + + // 从设备管理器中移除会话 + DeviceManager.getSeatSessions().remove(deviceBlockEntity.getBoundPlayerUUID()); + + // 检查玩家是否仍被其他设备绑定 + if (isPlayerBound(player.getUUID())) { + return; + } + + // 重置玩家的姿势状态 + DeviceManager.resetSeatSession(player); + } + } + } + } + + + +} diff --git a/src/main/templates/META-INF/mods.toml b/src/main/templates/META-INF/mods.toml new file mode 100644 index 00000000..5e8585bd --- /dev/null +++ b/src/main/templates/META-INF/mods.toml @@ -0,0 +1,112 @@ +# This is an example mods.toml file. It contains the data relating to the loading mods. +# There are several mandatory fields (#mandatory), and many more that are optional (#optional). +# The overall format is standard TOML format, v0.5.0. +# Note that there are a couple of TOML lists in this file. +# Find more information on toml format here: https://github.com/toml-lang/toml +# The animationName of the mod loader type to load - for regular FML @Mod mods it should be javafml +modLoader="javafml" #mandatory + +# A version range to match for said mod loader - for regular FML @Mod it will be the Forge version +loaderVersion="${loader_version_range}" #mandatory This is typically bumped every Minecraft version by Forge. See download page for lists of versions. + +# The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties. +# Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here. +license="${mod_license}" + +# A URL to refer people to when problems occur with this mod +#issueTrackerURL="https://change.me.to.your.issue.tracker.example.invalid/" #optional + +# If your mod is purely client-side and has no multiplayer functionality (be it dedicated servers or Open to LAN), +# set this to true, and Forge will set the correct displayTest for you and skip loading your mod on dedicated servers. +#clientSideOnly=true #optional - defaults to false if absent + +# A list of mods - how many allowed here is determined by the individual mod loader +[[mods]] #mandatory + +# The modid of the mod +modId="${mod_id}" #mandatory + +# The version number of the mod +version="${mod_version}" #mandatory + +# A display animationName for the mod +displayName="${mod_name}" #mandatory +# A URL to query for updates for this mod. See the JSON update specification https://docs.minecraftforge.net/en/latest/misc/updatechecker/ +#updateJSONURL="https://change.me.example.invalid/updates.json" #optional + +# A URL for the "homepage" for this mod, displayed in the mod UI +#displayURL="https://change.me.to.your.mods.homepage.example.invalid/" #optional + +# A file animationName (in the root of the mod JAR) containing a logo for display +#logoFile="lib39_logo.png" #optional + +# A text field displayed in the mod UI +credits="${mod_credits}" #optional + +# A text field displayed in the mod UI +authors="${mod_authors}" #optional + +# Display Test controls the display for your mod in the server connection screen +# MATCH_VERSION means that your mod will cause a red X if the versions on client and server differ. This is the default behaviour and should be what you choose if you have server and client elements to your mod. +# IGNORE_SERVER_VERSION means that your mod will not cause a red X if it's present on the server but not on the client. This is what you should use if you're a server only mod. +# IGNORE_ALL_VERSION means that your mod will not cause a red X if it's present on the client or the server. This is a special case and should only be used if your mod has no server component. +# NONE means that no display test is set on your mod. You need to do this yourself, see IExtensionPoint.DisplayTest for more information. You can define any scheme you wish with this value. +# IMPORTANT NOTE: this is NOT an instruction as to which environments (CLIENT or DEDICATED SERVER) your mod loads on. Your mod should load (and maybe do nothing!) whereever it finds itself. +#displayTest="MATCH_VERSION" # if nothing is specified, MATCH_VERSION is the default when clientSideOnly=false, otherwise IGNORE_ALL_VERSION when clientSideOnly=true (#optional) + +# The description text for the mod (multi line!) (#mandatory) +description='''${mod_description}''' + +# A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional. +[[dependencies.${mod_id}]] #optional + # the modid of the dependency + modId="forge" #mandatory + # Does this dependency have to exist - if not, ordering below must be specified + mandatory=true #mandatory + # The version range of the dependency + versionRange="${forge_version_range}" #mandatory + # An ordering relationship for the dependency - BEFORE or AFTER required if the dependency is not mandatory + # BEFORE - This mod is loaded BEFORE the dependency + # AFTER - This mod is loaded AFTER the dependency + ordering="NONE" + # Side this dependency is applied on - BOTH, CLIENT, or SERVER + side="BOTH" + +# Here's another dependency +[[dependencies.${mod_id}]] + modId="minecraft" + mandatory=true + # This version range declares a minimum of the current minecraft version up to but not including the next major version + versionRange="${minecraft_version_range}" + ordering="NONE" + side="BOTH" + +[[dependencies.${mod_id}]] +modId="lib39" +mandatory=true +# This version range declares a minimum of the current minecraft version up to but not including the next major version +versionRange="[${lib39_version},)" +ordering="AFTER" +side="BOTH" + +[[dependencies.${mod_id}]] +modId="curios" +mandatory=true +# This version range declares a minimum of the current minecraft version up to but not including the next major version +versionRange="[${curios_version},)" +ordering="NONE" +side="BOTH" + +[[dependencies.${mod_id}]] +modId="playeranimator" +mandatory=true +# This version range declares a minimum of the current minecraft version up to but not including the next major version +versionRange="[${player_animation_version},)" +ordering="NONE" +side="BOTH" + +# Features are specific properties of the game environment, that you may want to declare you require. This example declares +# that your mod requires GL version 3.2 or higher. Other features will be added. They are side aware so declaring this won't +# stop your mod loading on the server for example. +#[features.${mod_id}] +#openGLVersion="[3.2,)" diff --git a/src/main/templates/pack.mcmeta b/src/main/templates/pack.mcmeta new file mode 100644 index 00000000..ae03c9b7 --- /dev/null +++ b/src/main/templates/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "description": "${mod_name} resources", + "pack_format": 15 + } +}