初始化项目

This commit is contained in:
叁玖领域 2025-10-14 20:42:25 +08:00
commit 32ec10cfeb
43 changed files with 2542 additions and 0 deletions

25
.github/workflows/build.yml vendored Normal file
View File

@ -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

26
.gitignore vendored Normal file
View File

@ -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

21
LICENSE Normal file
View File

@ -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.

4
README.md Normal file
View File

@ -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.

275
build.gradle Normal file
View File

@ -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
}
}

52
gradle.properties Normal file
View File

@ -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= <PlaceHolder>Hello, Dungeon</PlaceHolder>
mod_credits=

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -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

252
gradlew vendored Normal file
View File

@ -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" "$@"

94
gradlew.bat vendored Normal file
View File

@ -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

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
libs/lib39-1.20.1-0.0.7.jar Normal file

Binary file not shown.

12
settings.gradle Normal file
View File

@ -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'
}

View File

@ -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);
}
}
}

View File

@ -0,0 +1,10 @@
package top.r3944realms.eroticdungeongame;
public class CommonHandler {
public static class Game extends CommonHandler {
}
public static class Mod extends CommonHandler {
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,4 @@
package top.r3944realms.eroticdungeongame.content.animation;
public class AnimationHandler implements IAnimationHandler {
}

View File

@ -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<>();
}
}

View File

@ -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;
}
}

View File

@ -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<Integer, AnimationProperties> SEAT_ANIMATIONS = new HashMap<>();
@Override
public void loadAllAnimations() {
loadSeatAnimations();
}
private void loadSeatAnimations() {
}
}

View File

@ -0,0 +1,7 @@
package top.r3944realms.eroticdungeongame.content.animation;
public interface IAnimationHandler {
default void loadAllAnimations() {
}
}

View File

@ -0,0 +1,7 @@
package top.r3944realms.eroticdungeongame.content.animation.type;
public interface IAnimationType {
String getTypeName();
String getAnimationName();
int getAnimalId();
}

View File

@ -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;
}
}

View File

@ -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<Direction, VoxelShape> 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<Block, BlockState> 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;
}
}

View File

@ -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<SeatPart> PART = EnumProperty.create("part", SeatPart.class);
protected Map<SeatPart, Map<Direction, VoxelShape>> 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<Block, BlockState> 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);
}
}

View File

@ -0,0 +1,8 @@
package top.r3944realms.eroticdungeongame.content.block;
public enum FurnitureType {
NORMAL,
HORIZONTAL_DOUBLE,
HORIZONTAL_TRIPLE,
VERTICAL_DOUBLE
}

View File

@ -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<SeatPart, Map<Direction, VoxelShape>> shapeMap = new EnumMap<>(SeatPart.class);
shapeMap.put(SeatPart.HEAD, ShapeUtil.createUniformDirectionMap(group1));
shapeMap.put(SeatPart.FOOT, ShapeUtil.createUniformDirectionMap(group2));
this.shapeMap = shapeMap;
}
}

View File

@ -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");
}
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,4 @@
package top.r3944realms.eroticdungeongame.content.device;
public class SeatSessionManager {
}

View File

@ -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<SeatEntity> 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;
}
}

View File

@ -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<SeatType, RegistryObject<EntityType<SeatEntity>>> entityTypeMap = new EnumMap<>(SeatType.class);
private final Map<SeatType, EntityType<SeatEntity>> cachedEntityTypes = new EnumMap<>(SeatType.class);
/**
* 注册座位类型与实体类型的映射
*/
public void register(@NotNull SeatType seatType, @NotNull RegistryObject<EntityType<SeatEntity>> entityType) {
entityTypeMap.put(seatType, entityType);
}
/**
* 获取座位类型对应的实体类型
*/
@NotNull
public EntityType<SeatEntity> getEntityType(@NotNull SeatType seatType) {
// 使用缓存避免重复获取
return cachedEntityTypes.computeIfAbsent(seatType, st -> {
RegistryObject<EntityType<SeatEntity>> 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<SeatType> getRegisteredSeatTypes() {
return entityTypeMap.keySet();
}
/**
* 清空缓存用于开发重载
*/
public void clearCache() {
cachedEntityTypes.clear();
}
}

View File

@ -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<? extends SeatEntity> 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<ClientGamePacketListener> 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();
}
}
}

View File

@ -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<BlockEntityType<?>> BLOCK_ENTITIES = DeferredRegister.create(ForgeRegistries.BLOCK_ENTITY_TYPES, EroticDungeon.MOD_ID);
@SuppressWarnings("DataFlowIssue")
public static final RegistryObject<BlockEntityType<SeatBlockEntity>> 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);
}
}

View File

@ -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<Block> BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, EroticDungeon.MOD_ID);
public static final List<RegistryObject<Block>> SEAT_BLOCKS = new ArrayList<>();
public static final RegistryObject<Block> 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);
}
}

View File

@ -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<CreativeModeTab> CREATIVE_TABS = DeferredRegister.create(Registries.CREATIVE_MODE_TAB, EroticDungeon.MOD_ID);
private static final RegistryObject<CreativeModeTab> MAIN = CREATIVE_TABS.register("main", () -> CreativeModeTab.builder()
.title(Component.literal("<Placeholder>"))
.icon(() -> Blocks.BARRIER.asItem().getDefaultInstance())
.displayItems((itemDisplayParameters, output) -> {})
.build()
);
public static void register(IEventBus eventBus) {
CREATIVE_TABS.register(eventBus);
}
}

View File

@ -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<EntityType<?>> ENTITIES = DeferredRegister.create(ForgeRegistries.ENTITY_TYPES, EroticDungeon.MOD_ID);
public static final RegistryObject<EntityType<SeatEntity>> CUFF_BED_SEAT = registerSeat(SeatType.CUFF_BED.getBlockName(), SeatEntity::new);
private static <T extends Entity> RegistryObject<EntityType<T>> registerSeat(String str, EntityType.EntityFactory<T> 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);
}
}

View File

@ -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<Item> ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, EroticDungeon.MOD_ID);
}

View File

@ -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<Block> 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<Entity> 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);
}
}
}
}
}

View File

@ -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,)"

View File

@ -0,0 +1,6 @@
{
"pack": {
"description": "${mod_name} resources",
"pack_format": 15
}
}