Initial commit: Forge 1.20.1 Ellie companion mod
- EllieEntity with GeckoLib animations, sleep AI, pathfinding with crouching - Dialog system with conditions and effects - Relationship system with milestones - OpenDoor and bed occupation pathfinding - 15 animations: idle1/2/3, sleep, walkingsimple, shiftwalking/shiftidle, etc.
This commit is contained in:
+128
@@ -0,0 +1,128 @@
|
||||
# User-specific stuff
|
||||
.idea/
|
||||
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Compiled class file
|
||||
*.class
|
||||
|
||||
# Log file
|
||||
*.log
|
||||
|
||||
# BlueJ files
|
||||
*.ctxt
|
||||
|
||||
# Package Files #
|
||||
*.jar
|
||||
*.war
|
||||
*.nar
|
||||
*.ear
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
|
||||
*~
|
||||
|
||||
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||
.fuse_hidden*
|
||||
|
||||
# KDE directory preferences
|
||||
.directory
|
||||
|
||||
# Linux trash folder which might appear on any partition or disk
|
||||
.Trash-*
|
||||
|
||||
# .nfs files are created when an open file is removed but is still being accessed
|
||||
.nfs*
|
||||
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
Thumbs.db:encryptable
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
.gradle
|
||||
build/
|
||||
|
||||
# Ignore Gradle GUI config
|
||||
gradle-app.setting
|
||||
|
||||
# Cache of project
|
||||
.gradletasknamecache
|
||||
|
||||
**/build/
|
||||
|
||||
# Common working directory
|
||||
run/
|
||||
runs/
|
||||
|
||||
# Forge Gradle
|
||||
crash-reports/
|
||||
logs/
|
||||
|
||||
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
|
||||
!gradle-wrapper.jar
|
||||
|
||||
# Secret files
|
||||
*.key
|
||||
*.pem
|
||||
.env
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
plugins {
|
||||
id 'eclipse'
|
||||
id 'idea'
|
||||
id 'net.minecraftforge.gradle' version '[6.0.16,6.2)'
|
||||
}
|
||||
|
||||
|
||||
group = mod_group_id
|
||||
version = mod_version
|
||||
|
||||
base {
|
||||
archivesName = mod_id
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain.languageVersion = JavaLanguageVersion.of(17)
|
||||
}
|
||||
|
||||
minecraft {
|
||||
// The mappings can be changed at any time and must be in the following format.
|
||||
// Channel: Version:
|
||||
// official MCVersion Official field/method names from Mojang mapping files
|
||||
// parchment YYYY.MM.DD-MCVersion Open community-sourced parameter names and javadocs layered on top of official
|
||||
//
|
||||
// You must be aware of the Mojang license when using the 'official' or 'parchment' mappings.
|
||||
// See more information here: https://github.com/MinecraftForge/MCPConfig/blob/master/Mojang.md
|
||||
//
|
||||
// Parchment is an unofficial project maintained by ParchmentMC, separate from MinecraftForge
|
||||
// Additional setup is needed to use their mappings: https://parchmentmc.org/docs/getting-started
|
||||
//
|
||||
// Use non-default mappings at your own risk. They may not always work.
|
||||
// Simply re-run your setup task after changing the mappings to update your workspace.
|
||||
mappings channel: mapping_channel, version: mapping_version
|
||||
|
||||
// When true, this property will have all Eclipse/IntelliJ IDEA run configurations run the "prepareX" task for the given run configuration before launching the game.
|
||||
// In most cases, it is not necessary to enable.
|
||||
// enableEclipsePrepareRuns = true
|
||||
// enableIdeaPrepareRuns = true
|
||||
|
||||
// This property allows configuring Gradle's ProcessResources task(s) to run on IDE output locations before launching the game.
|
||||
// It is REQUIRED to be set to true for this template to function.
|
||||
// See https://docs.gradle.org/current/dsl/org.gradle.language.jvm.tasks.ProcessResources.html
|
||||
copyIdeResources = true
|
||||
|
||||
// When true, this property will add the folder name of all declared run configurations to generated IDE run configurations.
|
||||
// The folder name can be set on a run configuration using the "folderName" property.
|
||||
// By default, the folder name of a run configuration is the name of the Gradle project containing it.
|
||||
// generateRunFolders = true
|
||||
|
||||
// This property enables access transformers for use in development.
|
||||
// They will be applied to the Minecraft artifact.
|
||||
// The access transformer file can be anywhere in the project.
|
||||
// However, it must be at "META-INF/accesstransformer.cfg" in the final mod jar to be loaded by Forge.
|
||||
// This default location is a best practice to automatically put the file in the right place in the final jar.
|
||||
// See https://docs.minecraftforge.net/en/latest/advanced/accesstransformers/ for more information.
|
||||
// accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg')
|
||||
|
||||
// Default run configurations.
|
||||
// These can be tweaked, removed, or duplicated as needed.
|
||||
runs {
|
||||
// applies to all the run configs below
|
||||
configureEach {
|
||||
workingDirectory project.file('run')
|
||||
|
||||
// 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.
|
||||
property '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
|
||||
property 'forge.logging.console.level', 'debug'
|
||||
|
||||
mods {
|
||||
"${mod_id}" {
|
||||
source sourceSets.main
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client {
|
||||
// Comma-separated list of namespaces to load gametests from. Empty = all namespaces.
|
||||
property 'forge.enabledGameTestNamespaces', mod_id
|
||||
}
|
||||
|
||||
server {
|
||||
property 'forge.enabledGameTestNamespaces', mod_id
|
||||
args '--nogui'
|
||||
}
|
||||
|
||||
// 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 {
|
||||
property 'forge.enabledGameTestNamespaces', mod_id
|
||||
}
|
||||
|
||||
data {
|
||||
// example of overriding the workingDirectory set in configureEach above
|
||||
workingDirectory project.file('run-data')
|
||||
|
||||
// Specify the modid for data generation, where to output the resulting resource, and where to look for existing resources.
|
||||
args '--mod', mod_id, '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources/')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Include resources generated by data generators.
|
||||
sourceSets.main.resources { srcDir 'src/generated/resources' }
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
name = 'GeckoLib'
|
||||
url = 'https://dl.cloudsmith.io/public/geckolib3/geckolib/maven/'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}"
|
||||
|
||||
implementation fg.deobf('software.bernie.geckolib:geckolib-forge-1.20.1:4.4.2')
|
||||
}
|
||||
|
||||
// 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.
|
||||
// When "copyIdeResources" is enabled, this will also run before the game launches in IDE environments.
|
||||
// See https://docs.gradle.org/current/dsl/org.gradle.language.jvm.tasks.ProcessResources.html
|
||||
tasks.named('processResources', ProcessResources).configure {
|
||||
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,]
|
||||
|
||||
inputs.properties replaceProperties
|
||||
|
||||
filesMatching(['META-INF/mods.toml', 'pack.mcmeta']) {
|
||||
expand replaceProperties + [project: project]
|
||||
}
|
||||
}
|
||||
|
||||
// Example for how to get properties into the manifest for reading at runtime.
|
||||
tasks.named('jar', Jar).configure {
|
||||
manifest {
|
||||
attributes(["Specification-Title" : mod_id,
|
||||
"Specification-Vendor" : mod_authors,
|
||||
"Specification-Version" : "1", // We are version 1 of ourselves
|
||||
"Implementation-Title" : project.name,
|
||||
"Implementation-Version" : project.jar.archiveVersion,
|
||||
"Implementation-Vendor" : mod_authors,
|
||||
"Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ")])
|
||||
}
|
||||
|
||||
// This is the preferred method to reobfuscate your jar file
|
||||
finalizedBy 'reobfJar'
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
org.gradle.jvmargs=-Xmx3G
|
||||
org.gradle.daemon=false
|
||||
# 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.4.20
|
||||
# The Forge version range can use any version of Forge as bounds or match the loader version range
|
||||
forge_version_range=[47,)
|
||||
# The loader version range can only use the major version of Forge/FML as bounds
|
||||
loader_version_range=[47,)
|
||||
# The mapping channel to use for mappings.
|
||||
# The default set of supported mapping channels are ["official", "snapshot", "snapshot_nodoc", "stable", "stable_nodoc"].
|
||||
# Additional mapping channels can be registered through the "channelProviders" extension in a Gradle plugin.
|
||||
#
|
||||
# | Channel | Version | |
|
||||
# |-----------|----------------------|--------------------------------------------------------------------------------|
|
||||
# | official | MCVersion | Official field/method names from Mojang mapping files |
|
||||
# | parchment | YYYY.MM.DD-MCVersion | Open community-sourced parameter names and javadocs layered on top of official |
|
||||
#
|
||||
# You must be aware of the Mojang license when using the 'official' or 'parchment' mappings.
|
||||
# See more information here: https://github.com/MinecraftForge/MCPConfig/blob/master/Mojang.md
|
||||
#
|
||||
# Parchment is an unofficial project maintained by ParchmentMC, separate from Minecraft Forge.
|
||||
# Additional setup is needed to use their mappings, see https://parchmentmc.org/docs/getting-started
|
||||
mapping_channel=official
|
||||
# The mapping version to query from the mapping channel.
|
||||
# This must match the format required by the mapping channel.
|
||||
mapping_version=1.20.1
|
||||
# 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=fabled_hearts
|
||||
# The human-readable display name for the mod.
|
||||
mod_name=Fabled Hearts
|
||||
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
|
||||
mod_license=All Rights Reserved
|
||||
# The mod version. See https://semver.org/
|
||||
mod_version=1.0
|
||||
# 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=me.sashegdev
|
||||
# The authors of the mod. This is a simple text string that is used for display purposes in the mod list.
|
||||
mod_authors=SashegDev
|
||||
# The description of the mod. This is a simple multiline text string that is used for display purposes in the mod list.
|
||||
mod_description=Жиза!
|
||||
Vendored
BIN
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
|
||||
@@ -0,0 +1,150 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
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 "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in
|
||||
CYGWIN* ) cygwin=true ;;
|
||||
Darwin* ) darwin=true ;;
|
||||
MSYS* | MINGW* ) msys=true ;;
|
||||
NonStop* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
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*)
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
;;
|
||||
esac
|
||||
case $MAX_FD in
|
||||
'' | soft) :;;
|
||||
*)
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stracks://github.com/gradle/gradle/issues/25036)
|
||||
# shellcheck disable=SC2153
|
||||
case $GRADLE_USER_HOME in
|
||||
'')
|
||||
if [ -z "$GRADLE_USER_HOME" ] ; then
|
||||
GRADLE_USER_HOME=$HOME/.gradle
|
||||
fi
|
||||
;;
|
||||
?:/*)
|
||||
GRADLE_USER_HOME=$( cygpath "$GRADLE_USER_HOME" ) ;;
|
||||
esac
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and/or backslashes, so put them in
|
||||
# temporary files and make the process fork them.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and/or backslashes, so put them in
|
||||
# temporary files and make the process fork them.
|
||||
# * Put the arguments into a shell array to preserve whitespace.
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xeli" is not available.
|
||||
if ! "$cygwin" && ! "$msys" && ! "$nonstop" ; then
|
||||
exec "$JAVACMD" "$@"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
Vendored
+81
@@ -0,0 +1,81 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 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 %OS%==Windows_NT endlocal
|
||||
|
||||
:omega
|
||||
@@ -0,0 +1,15 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
maven {
|
||||
name = 'MinecraftForge'
|
||||
url = 'https://maven.minecraftforge.net/'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0'
|
||||
}
|
||||
|
||||
rootProject.name = 'fabled-hearts'
|
||||
@@ -0,0 +1,85 @@
|
||||
package me.sashegdev.fabled_hearts;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import me.sashegdev.fabled_hearts.dialog.DialogLoader;
|
||||
import me.sashegdev.fabled_hearts.dialog.DialogManager;
|
||||
import me.sashegdev.fabled_hearts.entity.ellie.EllieEntity;
|
||||
import me.sashegdev.fabled_hearts.entity.ellie.EllieRenderer;
|
||||
import me.sashegdev.fabled_hearts.network.ModNetworking;
|
||||
import me.sashegdev.fabled_hearts.registry.ModEntities;
|
||||
import me.sashegdev.fabled_hearts.registry.ModItems;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.client.event.EntityRenderersEvent;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.event.AddReloadListenerEvent;
|
||||
import net.minecraftforge.event.entity.EntityAttributeCreationEvent;
|
||||
import net.minecraftforge.event.level.BlockEvent;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
|
||||
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
||||
import org.slf4j.Logger;
|
||||
import software.bernie.geckolib.GeckoLib;
|
||||
|
||||
@Mod(Main.MODID)
|
||||
public class Main {
|
||||
public static final String MODID = "fabled_hearts";
|
||||
public static final Logger LOGGER = LogUtils.getLogger();
|
||||
|
||||
public Main() {
|
||||
IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
|
||||
|
||||
GeckoLib.initialize();
|
||||
|
||||
new DialogManager();
|
||||
|
||||
ModEntities.register(modEventBus);
|
||||
ModItems.register(modEventBus);
|
||||
modEventBus.addListener(this::commonSetup);
|
||||
modEventBus.register(CommonHandler.class);
|
||||
modEventBus.register(ClientHandler.class);
|
||||
|
||||
MinecraftForge.EVENT_BUS.register(this);
|
||||
MinecraftForge.EVENT_BUS.register(new ForgeHandler());
|
||||
}
|
||||
|
||||
private void commonSetup(FMLCommonSetupEvent event) {
|
||||
event.enqueueWork(ModNetworking::register);
|
||||
}
|
||||
|
||||
@Mod.EventBusSubscriber(modid = MODID, bus = Mod.EventBusSubscriber.Bus.MOD)
|
||||
public static class CommonHandler {
|
||||
@SubscribeEvent
|
||||
public static void registerAttributes(EntityAttributeCreationEvent event) {
|
||||
event.put(ModEntities.ELLIE.get(), EllieEntity.createAttributes().build());
|
||||
}
|
||||
}
|
||||
|
||||
@Mod.EventBusSubscriber(modid = MODID, bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT)
|
||||
public static class ClientHandler {
|
||||
@SubscribeEvent
|
||||
public static void registerRenderers(EntityRenderersEvent.RegisterRenderers event) {
|
||||
event.registerEntityRenderer(ModEntities.ELLIE.get(), EllieRenderer::new);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ForgeHandler {
|
||||
@SubscribeEvent
|
||||
public void onAddReloadListeners(AddReloadListenerEvent event) {
|
||||
event.addListener(new DialogLoader());
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onBlockBreak(BlockEvent.BreakEvent event) {
|
||||
if (!event.getLevel().isClientSide()) {
|
||||
var pos = event.getPos();
|
||||
event.getLevel().getEntitiesOfClass(
|
||||
EllieEntity.class,
|
||||
new net.minecraft.world.phys.AABB(pos).inflate(2),
|
||||
e -> e.isSleeping() && pos.equals(e.getBedPos())
|
||||
).forEach(EllieEntity::wakeUp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package me.sashegdev.fabled_hearts.ai;
|
||||
|
||||
import net.minecraft.world.entity.PathfinderMob;
|
||||
import net.minecraft.world.entity.ai.goal.*;
|
||||
import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
|
||||
public class BasicAI {
|
||||
public static void addBasicGoals(PathfinderMob mob, GoalSelector selector, int start) {
|
||||
int p = start;
|
||||
selector.addGoal(p++, new FloatGoal(mob));
|
||||
selector.addGoal(p++, new LookAtPlayerGoal(mob, Player.class, 8.0f));
|
||||
selector.addGoal(p++, new RandomLookAroundGoal(mob));
|
||||
selector.addGoal(p++, new WaterAvoidingRandomStrollGoal(mob, 0.8));
|
||||
selector.addGoal(p++, new OpenDoorGoal(mob, true));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package me.sashegdev.fabled_hearts.ai;
|
||||
|
||||
import me.sashegdev.fabled_hearts.entity.ellie.EllieEntity;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.entity.ai.goal.Goal;
|
||||
import net.minecraft.world.level.block.DoorBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
public class EllieSleepGoal extends Goal {
|
||||
private final EllieEntity ellie;
|
||||
private final int searchRadius;
|
||||
private BlockPos bedPos;
|
||||
private int sleepTimer;
|
||||
private boolean claimed;
|
||||
private static final int MAX_SLEEP_TICKS = 6000;
|
||||
private static final double REACH_DIST_SQR = 1.8 * 1.8;
|
||||
|
||||
public EllieSleepGoal(EllieEntity ellie, int searchRadius) {
|
||||
this.ellie = ellie;
|
||||
this.searchRadius = searchRadius;
|
||||
this.setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canUse() {
|
||||
if (!ellie.level().isNight() && !ellie.isTired()) return false;
|
||||
if (ellie.isSleeping()) return false;
|
||||
if (ellie.hurtTime > 0) return false;
|
||||
bedPos = findNearestBed();
|
||||
return bedPos != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
claimed = false;
|
||||
if (bedPos != null) {
|
||||
if (ellie.isSpaceLow(bedPos)) {
|
||||
ellie.setPose(net.minecraft.world.entity.Pose.CROUCHING);
|
||||
}
|
||||
ellie.getNavigation().moveTo(bedPos.getX() + 0.5, bedPos.getY(), bedPos.getZ() + 0.5, 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
if (bedPos == null) return;
|
||||
|
||||
// Open doors in path
|
||||
tryOpenDoor();
|
||||
|
||||
double distSqr = ellie.distanceToSqr(Vec3.atCenterOf(bedPos));
|
||||
|
||||
if (distSqr < REACH_DIST_SQR) {
|
||||
ellie.getNavigation().stop();
|
||||
|
||||
if (!claimed) {
|
||||
claimed = ellie.occupyBed(bedPos);
|
||||
}
|
||||
|
||||
if (claimed) {
|
||||
if (!ellie.isSleeping()) {
|
||||
ellie.setBedSleepPos(bedPos);
|
||||
}
|
||||
sleepTimer++;
|
||||
}
|
||||
} else {
|
||||
if (ellie.getNavigation().isDone()) {
|
||||
ellie.getNavigation().moveTo(bedPos.getX() + 0.5, bedPos.getY(), bedPos.getZ() + 0.5, 0.6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void tryOpenDoor() {
|
||||
BlockPos inFront = ellie.blockPosition().relative(ellie.getDirection());
|
||||
for (int i = 0; i < 3; i++) {
|
||||
BlockPos checkPos = ellie.blockPosition().relative(ellie.getDirection(), i);
|
||||
BlockState state = ellie.level().getBlockState(checkPos);
|
||||
if (state.getBlock() instanceof DoorBlock) {
|
||||
if (!state.getValue(DoorBlock.OPEN)) {
|
||||
ellie.level().setBlock(checkPos, state.setValue(DoorBlock.OPEN, true), 3);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
if (ellie.isSleeping()) {
|
||||
ellie.wakeUp();
|
||||
}
|
||||
sleepTimer = 0;
|
||||
bedPos = null;
|
||||
claimed = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canContinueToUse() {
|
||||
if (ellie.hurtTime > 0) return false;
|
||||
if (ellie.isSleeping()) {
|
||||
if (sleepTimer >= MAX_SLEEP_TICKS) return false;
|
||||
if (bedPos != null && !ellie.level().getBlockState(bedPos).isBed(ellie.level(), bedPos, null)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!ellie.level().isNight() && !ellie.isTired()) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private BlockPos findNearestBed() {
|
||||
BlockPos entityPos = ellie.blockPosition();
|
||||
BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
|
||||
BlockPos nearest = null;
|
||||
double nearestDist = Double.MAX_VALUE;
|
||||
|
||||
for (int x = -searchRadius; x <= searchRadius; x++) {
|
||||
for (int z = -searchRadius; z <= searchRadius; z++) {
|
||||
for (int y = -2; y <= 2; y++) {
|
||||
mutable.set(entityPos.getX() + x, entityPos.getY() + y, entityPos.getZ() + z);
|
||||
if (isFreeBed(mutable)) {
|
||||
double dist = entityPos.distSqr(mutable);
|
||||
if (dist < nearestDist) {
|
||||
nearestDist = dist;
|
||||
nearest = mutable.immutable();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nearest;
|
||||
}
|
||||
|
||||
private boolean isFreeBed(BlockPos pos) {
|
||||
BlockState state = ellie.level().getBlockState(pos);
|
||||
if (!state.isBed(ellie.level(), pos, null)) return false;
|
||||
if (state.hasProperty(DoorBlock.OPEN) && state.getValue(DoorBlock.OPEN)) return false;
|
||||
return !state.getValue(net.minecraft.world.level.block.BedBlock.OCCUPIED);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package me.sashegdev.fabled_hearts.ai;
|
||||
|
||||
import me.sashegdev.fabled_hearts.entity.ellie.EllieEntity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.ai.goal.Goal;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
public class IdleAnimationGoal extends Goal {
|
||||
private final EllieEntity ellie;
|
||||
private int cooldown;
|
||||
private int animLength;
|
||||
|
||||
public IdleAnimationGoal(EllieEntity ellie) {
|
||||
this.ellie = ellie;
|
||||
this.setFlags(EnumSet.of(Flag.LOOK));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canUse() {
|
||||
if (cooldown > 0) {
|
||||
cooldown--;
|
||||
return false;
|
||||
}
|
||||
if (ellie.isSleeping()) return false;
|
||||
if (ellie.isUnderLowCeiling()) return false;
|
||||
if (ellie.isMoving()) return false;
|
||||
if (ellie.getRandom().nextInt(200) != 0) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
int animId = ellie.getRandom().nextInt(3) + 1;
|
||||
animLength = switch (animId) {
|
||||
case 1 -> 141;
|
||||
case 2 -> 231;
|
||||
case 3 -> 164;
|
||||
default -> 100;
|
||||
};
|
||||
ellie.triggerRandomIdle(animId);
|
||||
ellie.getNavigation().stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
animLength--;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
ellie.clearRandomIdle();
|
||||
cooldown = 100;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canContinueToUse() {
|
||||
return animLength > 0 && !ellie.isMoving() && !ellie.isUnderLowCeiling();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package me.sashegdev.fabled_hearts.api.dialog;
|
||||
|
||||
import me.sashegdev.fabled_hearts.entity.ellie.EllieEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
|
||||
public interface IDialogCondition {
|
||||
boolean test(EllieEntity ellie, Player player);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package me.sashegdev.fabled_hearts.api.dialog;
|
||||
|
||||
import me.sashegdev.fabled_hearts.entity.ellie.EllieEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
|
||||
public interface IDialogEffect {
|
||||
void apply(EllieEntity ellie, Player player);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package me.sashegdev.fabled_hearts.api.girl;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import software.bernie.geckolib.model.GeoModel;
|
||||
|
||||
public interface IGirlType {
|
||||
String getName();
|
||||
EntityType<?> getEntityType();
|
||||
ResourceLocation getModelLocation();
|
||||
ResourceLocation getTextureLocation();
|
||||
ResourceLocation getAnimationLocation();
|
||||
GeoModel<?> createModel();
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package me.sashegdev.fabled_hearts.api.girl;
|
||||
|
||||
import me.sashegdev.fabled_hearts.entity.ellie.EllieEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface INSFWAction {
|
||||
String getId();
|
||||
String getAnimationName();
|
||||
boolean canPerform(EllieEntity ellie, Player player);
|
||||
void perform(EllieEntity ellie, Player player);
|
||||
List<INSFWAction> getFollowUps();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package me.sashegdev.fabled_hearts.api.girl;
|
||||
|
||||
public interface IRelationship {
|
||||
float getPoints();
|
||||
float getMaxPoints();
|
||||
void addPoints(float amount);
|
||||
Milestone getCurrentMilestone();
|
||||
|
||||
enum Milestone {
|
||||
STRANGER(0),
|
||||
ACQUAINTANCE(50),
|
||||
FRIEND(75),
|
||||
LOVE(100);
|
||||
|
||||
final float threshold;
|
||||
Milestone(float t) { this.threshold = t; }
|
||||
public float getThreshold() { return threshold; }
|
||||
|
||||
public static Milestone fromPoints(float points) {
|
||||
Milestone result = STRANGER;
|
||||
for (var m : values()) {
|
||||
if (points >= m.threshold) result = m;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package me.sashegdev.fabled_hearts.data;
|
||||
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
|
||||
public enum RelationshipMilestones {
|
||||
STRANGER(0, "idleins"),
|
||||
ACQUAINTANCE(50, "idle_happy"),
|
||||
FRIEND(75, "idle_flirty"),
|
||||
LOVE(100, "idle_intimate");
|
||||
|
||||
private final float threshold;
|
||||
private final String dialogAnimation;
|
||||
|
||||
RelationshipMilestones(float threshold, String dialogAnimation) {
|
||||
this.threshold = threshold;
|
||||
this.dialogAnimation = dialogAnimation;
|
||||
}
|
||||
|
||||
public float getThreshold() { return threshold; }
|
||||
public String getDialogAnimation() { return dialogAnimation; }
|
||||
|
||||
public static RelationshipMilestones getMilestone(float relationship) {
|
||||
RelationshipMilestones current = STRANGER;
|
||||
for (var milestone : values()) {
|
||||
if (relationship >= milestone.threshold) {
|
||||
current = milestone;
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
public static boolean hasReached(float relationship, RelationshipMilestones milestone) {
|
||||
return relationship >= milestone.threshold;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package me.sashegdev.fabled_hearts.data;
|
||||
|
||||
import me.sashegdev.fabled_hearts.Main;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtUtils;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.saveddata.SavedData;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class WorldData extends SavedData {
|
||||
private static final String DATA_NAME = Main.MODID + "_world_data";
|
||||
private UUID ellieUUID;
|
||||
private int ellieId;
|
||||
|
||||
public WorldData() {}
|
||||
|
||||
public static WorldData get(ServerLevel level) {
|
||||
return level.getDataStorage().computeIfAbsent(WorldData::load, WorldData::new, DATA_NAME);
|
||||
}
|
||||
|
||||
public static WorldData load(CompoundTag tag) {
|
||||
WorldData data = new WorldData();
|
||||
if (tag.hasUUID("EllieUUID")) {
|
||||
data.ellieUUID = tag.getUUID("EllieUUID");
|
||||
}
|
||||
data.ellieId = tag.getInt("EllieId");
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag save(CompoundTag tag) {
|
||||
if (ellieUUID != null) {
|
||||
tag.putUUID("EllieUUID", ellieUUID);
|
||||
}
|
||||
tag.putInt("EllieId", ellieId);
|
||||
return tag;
|
||||
}
|
||||
|
||||
public boolean hasEllie() { return ellieUUID != null; }
|
||||
|
||||
@Nullable
|
||||
public UUID getEllieUUID() { return ellieUUID; }
|
||||
|
||||
public void setEllieUUID(UUID uuid) {
|
||||
this.ellieUUID = uuid;
|
||||
setDirty();
|
||||
}
|
||||
|
||||
public void removeEllie() {
|
||||
this.ellieUUID = null;
|
||||
setDirty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package me.sashegdev.fabled_hearts.dialog;
|
||||
|
||||
import com.google.gson.*;
|
||||
import me.sashegdev.fabled_hearts.Main;
|
||||
import me.sashegdev.fabled_hearts.api.dialog.IDialogCondition;
|
||||
import me.sashegdev.fabled_hearts.api.dialog.IDialogEffect;
|
||||
import me.sashegdev.fabled_hearts.dialog.conditions.*;
|
||||
import me.sashegdev.fabled_hearts.dialog.effects.*;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
import net.minecraft.server.packs.resources.SimplePreparableReloadListener;
|
||||
import net.minecraft.util.profiling.ProfilerFiller;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class DialogLoader extends SimplePreparableReloadListener<Map<String, DialogNode>> {
|
||||
private static final Gson GSON = new GsonBuilder().create();
|
||||
private static final Map<String, IDialogCondition> CONDITION_REGISTRY = new HashMap<>();
|
||||
private static final Map<String, IDialogEffect> EFFECT_REGISTRY = new HashMap<>();
|
||||
private static DialogLoader INSTANCE;
|
||||
private Map<String, DialogNode> dialogues = new HashMap<>();
|
||||
|
||||
public DialogLoader() {
|
||||
INSTANCE = this;
|
||||
}
|
||||
|
||||
public static DialogLoader get() { return INSTANCE; }
|
||||
|
||||
public static void registerCondition(String id, IDialogCondition condition) {
|
||||
CONDITION_REGISTRY.put(id, condition);
|
||||
}
|
||||
public static void registerEffect(String id, IDialogEffect effect) {
|
||||
EFFECT_REGISTRY.put(id, effect);
|
||||
}
|
||||
|
||||
static {
|
||||
registerCondition("relationship_min", new RelationshipCondition.Min());
|
||||
registerCondition("relationship_max", new RelationshipCondition.Max());
|
||||
registerCondition("is_tired", new FatigueCondition());
|
||||
registerCondition("is_night", new TimeCondition());
|
||||
registerCondition("first_dialog", new FirstDialogCondition());
|
||||
|
||||
registerEffect("add_relationship", new AddRelationshipEffect());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, DialogNode> prepare(ResourceManager manager, ProfilerFiller profiler) {
|
||||
Map<String, DialogNode> result = new HashMap<>();
|
||||
try {
|
||||
var resourceOpt = manager.getResource(new ResourceLocation(Main.MODID, "dialogues/dialogues.json"));
|
||||
if (resourceOpt.isEmpty()) return result;
|
||||
try (var reader = new BufferedReader(new InputStreamReader(resourceOpt.get().open()))) {
|
||||
JsonObject json = GSON.fromJson(reader, JsonObject.class);
|
||||
JsonArray nodes = json.getAsJsonArray("dialogues");
|
||||
for (var elem : nodes) {
|
||||
DialogNode node = parseNode(elem.getAsJsonObject());
|
||||
result.put(node.getId(), node);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Main.LOGGER.error("Failed to load dialogues", e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void apply(Map<String, DialogNode> loaded, ResourceManager manager, ProfilerFiller profiler) {
|
||||
this.dialogues = loaded;
|
||||
Main.LOGGER.info("Loaded {} dialogues", loaded.size());
|
||||
}
|
||||
|
||||
private DialogNode parseNode(JsonObject obj) {
|
||||
String id = obj.get("id").getAsString();
|
||||
String text = obj.get("text").getAsString();
|
||||
String animation = obj.has("animation") ? obj.get("animation").getAsString() : "idleins";
|
||||
|
||||
List<DialogNode.DialogChoice> choices = new ArrayList<>();
|
||||
if (obj.has("choices")) {
|
||||
JsonArray choicesArr = obj.getAsJsonArray("choices");
|
||||
for (var choiceElem : choicesArr) {
|
||||
choices.add(parseChoice(choiceElem.getAsJsonObject()));
|
||||
}
|
||||
}
|
||||
return new DialogNode(id, text, animation, choices);
|
||||
}
|
||||
|
||||
private DialogNode.DialogChoice parseChoice(JsonObject obj) {
|
||||
String text = obj.get("text").getAsString();
|
||||
String next = obj.get("next").getAsString();
|
||||
|
||||
List<IDialogCondition> conditions = new ArrayList<>();
|
||||
if (obj.has("conditions")) {
|
||||
for (var cond : obj.getAsJsonArray("conditions")) {
|
||||
JsonObject condObj = cond.getAsJsonObject();
|
||||
String type = condObj.get("type").getAsString();
|
||||
IDialogCondition condition = CONDITION_REGISTRY.get(type);
|
||||
if (condition instanceof IConditionWithJson configurable) {
|
||||
configurable.configure(condObj);
|
||||
}
|
||||
if (condition != null) conditions.add(condition);
|
||||
}
|
||||
}
|
||||
|
||||
List<IDialogEffect> effects = new ArrayList<>();
|
||||
if (obj.has("effects")) {
|
||||
for (var eff : obj.getAsJsonArray("effects")) {
|
||||
JsonObject effObj = eff.getAsJsonObject();
|
||||
String type = effObj.get("type").getAsString();
|
||||
IDialogEffect effect = EFFECT_REGISTRY.get(type);
|
||||
if (effect instanceof IEffectWithJson configurable) {
|
||||
configurable.configure(effObj);
|
||||
}
|
||||
if (effect != null) effects.add(effect);
|
||||
}
|
||||
}
|
||||
|
||||
return new DialogNode.DialogChoice(text, next, conditions, effects);
|
||||
}
|
||||
|
||||
public DialogNode getNode(String id) { return dialogues.get(id); }
|
||||
public Collection<DialogNode> getAllNodes() { return dialogues.values(); }
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package me.sashegdev.fabled_hearts.dialog;
|
||||
|
||||
import me.sashegdev.fabled_hearts.api.dialog.IDialogEffect;
|
||||
import me.sashegdev.fabled_hearts.entity.ellie.EllieEntity;
|
||||
import me.sashegdev.fabled_hearts.network.DialogNodePacket;
|
||||
import me.sashegdev.fabled_hearts.network.ModNetworking;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraftforge.network.PacketDistributor;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class DialogManager {
|
||||
private static DialogManager INSTANCE;
|
||||
private final Map<UUID, Map<Integer, String>> playerDialogState = new HashMap<>();
|
||||
|
||||
public DialogManager() {
|
||||
INSTANCE = this;
|
||||
}
|
||||
public static DialogManager get() { return INSTANCE; }
|
||||
|
||||
public void startDialog(EllieEntity ellie, Player player) {
|
||||
String startNode = getStartNode(ellie, player);
|
||||
if (startNode == null) return;
|
||||
playerDialogState.computeIfAbsent(player.getUUID(), k -> new HashMap<>())
|
||||
.put(ellie.getId(), startNode);
|
||||
sendNode(ellie, player, startNode);
|
||||
}
|
||||
|
||||
public void makeChoice(EllieEntity ellie, Player player, int choiceIndex) {
|
||||
var state = playerDialogState.get(player.getUUID());
|
||||
if (state == null) return;
|
||||
String currentId = state.get(ellie.getId());
|
||||
if (currentId == null) return;
|
||||
|
||||
DialogNode node = DialogLoader.get().getNode(currentId);
|
||||
if (node == null || choiceIndex < 0 || choiceIndex >= node.getChoices().size()) return;
|
||||
|
||||
var choice = node.getChoices().get(choiceIndex);
|
||||
|
||||
for (var condition : choice.getConditions()) {
|
||||
if (!condition.test(ellie, player)) return;
|
||||
}
|
||||
for (var effect : choice.getEffects()) {
|
||||
effect.apply(ellie, player);
|
||||
}
|
||||
|
||||
String nextId = choice.getNextNodeId();
|
||||
if (nextId.equals("__end__")) {
|
||||
state.remove(ellie.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
state.put(ellie.getId(), nextId);
|
||||
sendNode(ellie, player, nextId);
|
||||
}
|
||||
|
||||
private void sendNode(EllieEntity ellie, Player player, String nodeId) {
|
||||
DialogNode node = DialogLoader.get().getNode(nodeId);
|
||||
if (node == null) return;
|
||||
|
||||
ModNetworking.CHANNEL.send(
|
||||
PacketDistributor.PLAYER.with(() -> (net.minecraft.server.level.ServerPlayer) player),
|
||||
new DialogNodePacket(ellie.getId(), node)
|
||||
);
|
||||
}
|
||||
|
||||
private String getStartNode(EllieEntity ellie, Player player) {
|
||||
for (DialogNode node : DialogLoader.get().getAllNodes()) {
|
||||
boolean allMatch = true;
|
||||
boolean hasConditions = false;
|
||||
for (var choice : node.getChoices()) {
|
||||
for (var cond : choice.getConditions()) {
|
||||
hasConditions = true;
|
||||
if (!cond.test(ellie, player)) {
|
||||
allMatch = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!allMatch) break;
|
||||
}
|
||||
if (hasConditions && allMatch) return node.getId();
|
||||
}
|
||||
return "greeting";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package me.sashegdev.fabled_hearts.dialog;
|
||||
|
||||
import me.sashegdev.fabled_hearts.api.dialog.IDialogCondition;
|
||||
import me.sashegdev.fabled_hearts.api.dialog.IDialogEffect;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class DialogNode {
|
||||
private final String id;
|
||||
private final String text;
|
||||
private final String animation;
|
||||
private final List<DialogChoice> choices;
|
||||
|
||||
public DialogNode(String id, String text, String animation, List<DialogChoice> choices) {
|
||||
this.id = id;
|
||||
this.text = text;
|
||||
this.animation = animation;
|
||||
this.choices = choices;
|
||||
}
|
||||
|
||||
public String getId() { return id; }
|
||||
public String getText() { return text; }
|
||||
public String getAnimation() { return animation; }
|
||||
public List<DialogChoice> getChoices() { return choices; }
|
||||
|
||||
public static class DialogChoice {
|
||||
private final String text;
|
||||
private final String nextNodeId;
|
||||
private final List<IDialogCondition> conditions;
|
||||
private final List<IDialogEffect> effects;
|
||||
|
||||
public DialogChoice(String text, String nextNodeId,
|
||||
List<IDialogCondition> conditions,
|
||||
List<IDialogEffect> effects) {
|
||||
this.text = text;
|
||||
this.nextNodeId = nextNodeId;
|
||||
this.conditions = conditions;
|
||||
this.effects = effects;
|
||||
}
|
||||
|
||||
public String getText() { return text; }
|
||||
public String getNextNodeId() { return nextNodeId; }
|
||||
public List<IDialogCondition> getConditions() { return conditions; }
|
||||
public List<IDialogEffect> getEffects() { return effects; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package me.sashegdev.fabled_hearts.dialog;
|
||||
|
||||
import me.sashegdev.fabled_hearts.Main;
|
||||
import me.sashegdev.fabled_hearts.entity.ellie.EllieEntity;
|
||||
import me.sashegdev.fabled_hearts.network.DialogChoicePacket;
|
||||
import me.sashegdev.fabled_hearts.network.ModNetworking;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class DialogScreen extends Screen {
|
||||
private static final ResourceLocation BG = new ResourceLocation(Main.MODID, "textures/gui/dialog_bg.png");
|
||||
|
||||
private final EllieEntity ellie;
|
||||
private final String dialogText;
|
||||
private final List<String> choices;
|
||||
private int textAnimTicks;
|
||||
|
||||
public DialogScreen(EllieEntity ellie, String dialogText, List<String> choices) {
|
||||
super(Component.literal("Dialog"));
|
||||
this.ellie = ellie;
|
||||
this.dialogText = dialogText;
|
||||
this.choices = choices;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
int centerX = width / 2;
|
||||
int baseY = height - 100;
|
||||
|
||||
for (int i = 0; i < choices.size(); i++) {
|
||||
final int idx = i;
|
||||
int y = baseY + i * 25;
|
||||
addRenderableWidget(Button.builder(
|
||||
Component.literal("§e▸ " + choices.get(i)),
|
||||
btn -> selectChoice(idx)
|
||||
).bounds(centerX - 150, y, 300, 20).build());
|
||||
}
|
||||
}
|
||||
|
||||
private void selectChoice(int index) {
|
||||
ModNetworking.CHANNEL.sendToServer(new DialogChoicePacket(ellie.getId(), index));
|
||||
Minecraft.getInstance().setScreen(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(GuiGraphics gui, int mouseX, int mouseY, float partialTick) {
|
||||
renderBackground(gui);
|
||||
super.render(gui, mouseX, mouseY, partialTick);
|
||||
|
||||
int centerX = width / 2;
|
||||
textAnimTicks++;
|
||||
int charsToShow = Math.min(textAnimTicks / 2, dialogText.length());
|
||||
String displayed = dialogText.substring(0, charsToShow);
|
||||
|
||||
int textY = 30;
|
||||
gui.drawString(font, "§l§dEllie", centerX - 150, textY, 0xFFFFFF);
|
||||
gui.drawWordWrap(font, Component.literal(displayed), centerX - 150, textY + 20, 300, 0xEEEEEE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPauseScreen() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package me.sashegdev.fabled_hearts.dialog.conditions;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import me.sashegdev.fabled_hearts.api.dialog.IDialogCondition;
|
||||
import me.sashegdev.fabled_hearts.entity.ellie.EllieEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
|
||||
public class FatigueCondition implements IDialogCondition, IConditionWithJson {
|
||||
private int maxTicks = 48000;
|
||||
|
||||
@Override
|
||||
public void configure(JsonObject obj) {
|
||||
if (obj.has("max_ticks")) this.maxTicks = obj.get("max_ticks").getAsInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(EllieEntity ellie, Player player) {
|
||||
return ellie.getTicksWithoutSleep() <= maxTicks;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package me.sashegdev.fabled_hearts.dialog.conditions;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import me.sashegdev.fabled_hearts.api.dialog.IDialogCondition;
|
||||
import me.sashegdev.fabled_hearts.entity.ellie.EllieEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
public class FirstDialogCondition implements IDialogCondition, IConditionWithJson {
|
||||
private static final Set<UUID> talkedTo = new HashSet<>();
|
||||
|
||||
@Override
|
||||
public void configure(JsonObject obj) {}
|
||||
|
||||
@Override
|
||||
public boolean test(EllieEntity ellie, Player player) {
|
||||
return !talkedTo.contains(player.getUUID());
|
||||
}
|
||||
|
||||
public static void markTalked(Player player) {
|
||||
talkedTo.add(player.getUUID());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package me.sashegdev.fabled_hearts.dialog.conditions;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
public interface IConditionWithJson {
|
||||
void configure(JsonObject obj);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package me.sashegdev.fabled_hearts.dialog.conditions;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import me.sashegdev.fabled_hearts.api.dialog.IDialogCondition;
|
||||
import me.sashegdev.fabled_hearts.entity.ellie.EllieEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
|
||||
public class RelationshipCondition {
|
||||
|
||||
public static class Min implements IDialogCondition, IConditionWithJson {
|
||||
private float min = 0;
|
||||
|
||||
@Override
|
||||
public void configure(JsonObject obj) {
|
||||
if (obj.has("value")) this.min = obj.get("value").getAsFloat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(EllieEntity ellie, Player player) {
|
||||
return ellie.getRelationshipPoints() >= min;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Max implements IDialogCondition, IConditionWithJson {
|
||||
private float max = 100;
|
||||
|
||||
@Override
|
||||
public void configure(JsonObject obj) {
|
||||
if (obj.has("value")) this.max = obj.get("value").getAsFloat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(EllieEntity ellie, Player player) {
|
||||
return ellie.getRelationshipPoints() <= max;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package me.sashegdev.fabled_hearts.dialog.conditions;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import me.sashegdev.fabled_hearts.api.dialog.IDialogCondition;
|
||||
import me.sashegdev.fabled_hearts.entity.ellie.EllieEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
|
||||
public class TimeCondition implements IDialogCondition, IConditionWithJson {
|
||||
private boolean night = false;
|
||||
|
||||
@Override
|
||||
public void configure(JsonObject obj) {
|
||||
if (obj.has("night")) this.night = obj.get("night").getAsBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(EllieEntity ellie, Player player) {
|
||||
return ellie.level().isNight() == night;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package me.sashegdev.fabled_hearts.dialog.effects;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import me.sashegdev.fabled_hearts.api.dialog.IDialogEffect;
|
||||
import me.sashegdev.fabled_hearts.entity.ellie.EllieEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
|
||||
public class AddRelationshipEffect implements IDialogEffect, IEffectWithJson {
|
||||
private float amount = 5;
|
||||
private boolean markFirstDialog = false;
|
||||
|
||||
@Override
|
||||
public void configure(JsonObject obj) {
|
||||
if (obj.has("value")) this.amount = obj.get("value").getAsFloat();
|
||||
if (obj.has("mark_first_dialog")) this.markFirstDialog = obj.get("mark_first_dialog").getAsBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(EllieEntity ellie, Player player) {
|
||||
ellie.addRelationshipPoints(amount);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package me.sashegdev.fabled_hearts.dialog.effects;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
public interface IEffectWithJson {
|
||||
void configure(JsonObject obj);
|
||||
}
|
||||
@@ -0,0 +1,430 @@
|
||||
package me.sashegdev.fabled_hearts.entity.ellie;
|
||||
|
||||
import me.sashegdev.fabled_hearts.ai.BasicAI;
|
||||
import me.sashegdev.fabled_hearts.ai.EllieSleepGoal;
|
||||
import me.sashegdev.fabled_hearts.ai.IdleAnimationGoal;
|
||||
import me.sashegdev.fabled_hearts.network.ModNetworking;
|
||||
import me.sashegdev.fabled_hearts.network.OpenDialogPacket;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.syncher.EntityDataAccessor;
|
||||
import net.minecraft.network.syncher.EntityDataSerializers;
|
||||
import net.minecraft.network.syncher.SynchedEntityData;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.entity.*;
|
||||
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
|
||||
import net.minecraft.world.entity.ai.attributes.Attributes;
|
||||
import net.minecraft.world.entity.ai.navigation.GroundPathNavigation;
|
||||
import net.minecraft.world.entity.animal.Animal;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.BedBlock;
|
||||
import net.minecraft.world.level.block.DoorBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.properties.BedPart;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import software.bernie.geckolib.animatable.GeoEntity;
|
||||
import software.bernie.geckolib.core.animatable.instance.AnimatableInstanceCache;
|
||||
import software.bernie.geckolib.core.animation.AnimatableManager;
|
||||
import software.bernie.geckolib.core.animation.AnimationController;
|
||||
import software.bernie.geckolib.core.animation.AnimationState;
|
||||
import software.bernie.geckolib.core.animation.RawAnimation;
|
||||
import software.bernie.geckolib.core.object.PlayState;
|
||||
import software.bernie.geckolib.util.GeckoLibUtil;
|
||||
|
||||
public class EllieEntity extends Animal implements GeoEntity {
|
||||
private static final EntityDataAccessor<Boolean> DATA_TIRED =
|
||||
SynchedEntityData.defineId(EllieEntity.class, EntityDataSerializers.BOOLEAN);
|
||||
private static final EntityDataAccessor<Boolean> DATA_SLEEPING =
|
||||
SynchedEntityData.defineId(EllieEntity.class, EntityDataSerializers.BOOLEAN);
|
||||
private static final EntityDataAccessor<Boolean> DATA_IN_RAIN =
|
||||
SynchedEntityData.defineId(EllieEntity.class, EntityDataSerializers.BOOLEAN);
|
||||
private static final EntityDataAccessor<Boolean> DATA_UNDER_LOW_CEILING =
|
||||
SynchedEntityData.defineId(EllieEntity.class, EntityDataSerializers.BOOLEAN);
|
||||
private static final EntityDataAccessor<Integer> DATA_IDLE_ANIM =
|
||||
SynchedEntityData.defineId(EllieEntity.class, EntityDataSerializers.INT);
|
||||
private static final EntityDataAccessor<Boolean> DATA_EATING =
|
||||
SynchedEntityData.defineId(EllieEntity.class, EntityDataSerializers.BOOLEAN);
|
||||
private static final EntityDataAccessor<Integer> DATA_FALL_TICKS =
|
||||
SynchedEntityData.defineId(EllieEntity.class, EntityDataSerializers.INT);
|
||||
private static final EntityDataAccessor<Boolean> DATA_MOVING =
|
||||
SynchedEntityData.defineId(EllieEntity.class, EntityDataSerializers.BOOLEAN);
|
||||
|
||||
private final AnimatableInstanceCache cache = GeckoLibUtil.createInstanceCache(this);
|
||||
|
||||
private int ticksWithoutSleep;
|
||||
private float relationshipPoints;
|
||||
private BlockPos bedPos;
|
||||
private double lastX, lastZ;
|
||||
private int moveDetectCooldown;
|
||||
private BlockPos lastOpenedDoor;
|
||||
private int doorCloseTimer;
|
||||
private int crouchTimer;
|
||||
|
||||
public EllieEntity(EntityType<? extends Animal> type, Level level) {
|
||||
super(type, level);
|
||||
((GroundPathNavigation) this.getNavigation()).setCanOpenDoors(true);
|
||||
this.setPersistenceRequired();
|
||||
}
|
||||
|
||||
public static AttributeSupplier.Builder createAttributes() {
|
||||
return Animal.createMobAttributes()
|
||||
.add(Attributes.MAX_HEALTH, 40.0)
|
||||
.add(Attributes.MOVEMENT_SPEED, 0.23)
|
||||
.add(Attributes.FOLLOW_RANGE, 16.0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void defineSynchedData() {
|
||||
super.defineSynchedData();
|
||||
this.entityData.define(DATA_TIRED, false);
|
||||
this.entityData.define(DATA_SLEEPING, false);
|
||||
this.entityData.define(DATA_IN_RAIN, false);
|
||||
this.entityData.define(DATA_UNDER_LOW_CEILING, false);
|
||||
this.entityData.define(DATA_IDLE_ANIM, 0);
|
||||
this.entityData.define(DATA_EATING, false);
|
||||
this.entityData.define(DATA_FALL_TICKS, 0);
|
||||
this.entityData.define(DATA_MOVING, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerGoals() {
|
||||
BasicAI.addBasicGoals(this, this.goalSelector, 2);
|
||||
this.goalSelector.addGoal(1, new EllieSleepGoal(this, 20));
|
||||
this.goalSelector.addGoal(1, new IdleAnimationGoal(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void aiStep() {
|
||||
super.aiStep();
|
||||
tickMovementDetection();
|
||||
tickDoors();
|
||||
if (!level().isClientSide) {
|
||||
tickServer();
|
||||
}
|
||||
}
|
||||
|
||||
private void tickMovementDetection() {
|
||||
if (moveDetectCooldown-- <= 0) {
|
||||
moveDetectCooldown = 2;
|
||||
double dx = getX() - lastX;
|
||||
double dz = getZ() - lastZ;
|
||||
lastX = getX();
|
||||
lastZ = getZ();
|
||||
}
|
||||
}
|
||||
|
||||
private void tickDoors() {
|
||||
if (lastOpenedDoor != null) {
|
||||
BlockState state = level().getBlockState(lastOpenedDoor);
|
||||
if (state.getBlock() instanceof DoorBlock && state.getValue(DoorBlock.OPEN)) {
|
||||
double dist = distanceToSqr(lastOpenedDoor.getX() + 0.5, lastOpenedDoor.getY(), lastOpenedDoor.getZ() + 0.5);
|
||||
if (dist > 0.5 * 0.5) {
|
||||
doorCloseTimer++;
|
||||
if (doorCloseTimer > 10) {
|
||||
level().setBlock(lastOpenedDoor, state.setValue(DoorBlock.OPEN, false), 3);
|
||||
lastOpenedDoor = null;
|
||||
doorCloseTimer = 0;
|
||||
}
|
||||
} else {
|
||||
doorCloseTimer = 0;
|
||||
}
|
||||
} else {
|
||||
lastOpenedDoor = null;
|
||||
doorCloseTimer = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isMoving() {
|
||||
double dx = getX() - lastX;
|
||||
double dz = getZ() - lastZ;
|
||||
boolean posChanged = dx * dx + dz * dz > 1.0e-8;
|
||||
boolean navBusy = navigation.isInProgress() && !navigation.isDone();
|
||||
boolean hasVelocity = getDeltaMovement().horizontalDistanceSqr() > 0.001;
|
||||
return posChanged || navBusy || hasVelocity;
|
||||
}
|
||||
|
||||
private void tickServer() {
|
||||
boolean isNight = level().isNight();
|
||||
boolean raining = level().isRainingAt(blockPosition());
|
||||
boolean lowCeiling = checkLowCeiling();
|
||||
boolean tired = ticksWithoutSleep > 48000;
|
||||
boolean sleeping = entityData.get(DATA_SLEEPING);
|
||||
boolean falling = !onGround() && getDeltaMovement().y < -0.1;
|
||||
boolean moving = isMoving();
|
||||
|
||||
entityData.set(DATA_MOVING, moving);
|
||||
entityData.set(DATA_IN_RAIN, raining);
|
||||
entityData.set(DATA_UNDER_LOW_CEILING, lowCeiling);
|
||||
entityData.set(DATA_TIRED, tired);
|
||||
|
||||
if (falling) {
|
||||
entityData.set(DATA_FALL_TICKS, entityData.get(DATA_FALL_TICKS) + 1);
|
||||
} else {
|
||||
entityData.set(DATA_FALL_TICKS, 0);
|
||||
}
|
||||
|
||||
boolean wasCrouching = getPose() == Pose.CROUCHING;
|
||||
if (wasCrouching) {
|
||||
crouchTimer = Math.max(0, crouchTimer - 1);
|
||||
}
|
||||
|
||||
if (sleeping) {
|
||||
setPose(Pose.SLEEPING);
|
||||
crouchTimer = 0;
|
||||
} else if (lowCeiling) {
|
||||
if (onGround() || wasCrouching) {
|
||||
setPose(Pose.CROUCHING);
|
||||
crouchTimer = 20;
|
||||
}
|
||||
} else if (wasCrouching && crouchTimer > 0) {
|
||||
setPose(Pose.CROUCHING);
|
||||
} else {
|
||||
if (getPose() != Pose.STANDING) {
|
||||
setPose(Pose.STANDING);
|
||||
}
|
||||
}
|
||||
|
||||
if (!sleeping) {
|
||||
if (isNight) {
|
||||
ticksWithoutSleep++;
|
||||
}
|
||||
} else {
|
||||
ticksWithoutSleep = Math.max(0, ticksWithoutSleep - 10);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkLowCeiling() {
|
||||
if (isSpaceLow(blockPosition())) return true;
|
||||
|
||||
for (Direction dir : Direction.Plane.HORIZONTAL) {
|
||||
for (int i = 2; i <= 4; i++) {
|
||||
if (isSpaceLow(blockPosition().relative(dir, i))) return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (navigation.isInProgress()) {
|
||||
var path = navigation.getPath();
|
||||
if (path != null) {
|
||||
for (int i = 0; i < path.getNodeCount(); i++) {
|
||||
BlockPos nodePos = path.getNodePos(i);
|
||||
double dist = distanceToSqr(
|
||||
nodePos.getX() + 0.5, nodePos.getY(), nodePos.getZ() + 0.5);
|
||||
if (dist < 3 * 3 && isSpaceLow(nodePos)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isSpaceLow(BlockPos pos) {
|
||||
return level().getBlockState(pos.above(1)).blocksMotion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityDimensions getDimensions(Pose pose) {
|
||||
if (pose == Pose.SLEEPING) return EntityDimensions.fixed(0.4f, 0.3f);
|
||||
if (pose == Pose.CROUCHING) return EntityDimensions.fixed(0.6f, 1.5f);
|
||||
return super.getDimensions(pose);
|
||||
}
|
||||
|
||||
public boolean occupyBed(BlockPos pos) {
|
||||
BlockState state = level().getBlockState(pos);
|
||||
if (state.getBlock() instanceof BedBlock) {
|
||||
if (state.getValue(BedBlock.OCCUPIED)) return false;
|
||||
BlockPos foot = state.getValue(BedBlock.PART) == BedPart.HEAD
|
||||
? pos.relative(state.getValue(BedBlock.FACING).getOpposite())
|
||||
: pos;
|
||||
level().setBlock(foot, state.setValue(BedBlock.OCCUPIED, true), 3);
|
||||
BlockPos head = foot.relative(state.getValue(BedBlock.FACING));
|
||||
BlockState headState = level().getBlockState(head);
|
||||
if (headState.getBlock() instanceof BedBlock) {
|
||||
level().setBlock(head, headState.setValue(BedBlock.OCCUPIED, true), 3);
|
||||
}
|
||||
bedPos = foot;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void releaseBed() {
|
||||
if (bedPos != null) {
|
||||
BlockState footState = level().getBlockState(bedPos);
|
||||
if (footState.getBlock() instanceof BedBlock) {
|
||||
Direction facing = footState.getValue(BedBlock.FACING);
|
||||
for (BlockPos bp : new BlockPos[]{bedPos, bedPos.relative(facing)}) {
|
||||
BlockState s = level().getBlockState(bp);
|
||||
if (s.getBlock() instanceof BedBlock) {
|
||||
level().setBlock(bp, s.setValue(BedBlock.OCCUPIED, false), 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
bedPos = null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isBedAt(BlockPos pos) {
|
||||
return level().getBlockState(pos).isBed(level(), pos, null);
|
||||
}
|
||||
|
||||
public void setBedSleepPos(BlockPos pos) {
|
||||
BlockState state = level().getBlockState(pos);
|
||||
if (!(state.getBlock() instanceof BedBlock)) return;
|
||||
|
||||
Direction facing = state.getValue(BedBlock.FACING);
|
||||
BlockPos footPos = state.getValue(BedBlock.PART) == BedPart.HEAD
|
||||
? pos.relative(facing.getOpposite())
|
||||
: pos;
|
||||
|
||||
float yRot = facing.toYRot();
|
||||
setPos(footPos.getX() + 0.5, footPos.getY() + 0.5625, footPos.getZ() + 0.5);
|
||||
setYRot(yRot);
|
||||
yRotO = yRot;
|
||||
setYHeadRot(yRot);
|
||||
setSleeping(true);
|
||||
setPose(Pose.SLEEPING);
|
||||
}
|
||||
|
||||
public void wakeUp() {
|
||||
releaseBed();
|
||||
setSleeping(false);
|
||||
setPose(Pose.STANDING);
|
||||
navigation.stop();
|
||||
setDeltaMovement(getDeltaMovement().add(0, 0.15, 0));
|
||||
}
|
||||
|
||||
public void openDoorInFront() {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
BlockPos checkPos = blockPosition().relative(getDirection(), 2 + i);
|
||||
BlockState state = level().getBlockState(checkPos);
|
||||
if (state.getBlock() instanceof DoorBlock) {
|
||||
if (!state.getValue(DoorBlock.OPEN)) {
|
||||
level().setBlock(checkPos, state.setValue(DoorBlock.OPEN, true), 3);
|
||||
lastOpenedDoor = checkPos;
|
||||
doorCloseTimer = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
|
||||
controllers.add(new AnimationController<>(this, "main", 4, this::animationHandler));
|
||||
}
|
||||
|
||||
private <E extends GeoEntity> PlayState animationHandler(AnimationState<E> state) {
|
||||
int idleAnim = entityData.get(DATA_IDLE_ANIM);
|
||||
if (idleAnim > 0) {
|
||||
String animName = switch (idleAnim) {
|
||||
case 1 -> "idle1";
|
||||
case 2 -> "idle2";
|
||||
case 3 -> "idle3";
|
||||
default -> null;
|
||||
};
|
||||
if (animName != null) {
|
||||
return state.setAndContinue(RawAnimation.begin().thenPlay(animName));
|
||||
}
|
||||
}
|
||||
|
||||
RawAnimation anim = resolveAnimationRaw();
|
||||
return state.setAndContinue(anim);
|
||||
}
|
||||
|
||||
public void triggerRandomIdle(int id) {
|
||||
if (id >= 1 && id <= 3) {
|
||||
entityData.set(DATA_IDLE_ANIM, id);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearRandomIdle() {
|
||||
entityData.set(DATA_IDLE_ANIM, 0);
|
||||
}
|
||||
|
||||
private RawAnimation resolveAnimationRaw() {
|
||||
boolean tired = entityData.get(DATA_TIRED);
|
||||
boolean sleeping = entityData.get(DATA_SLEEPING);
|
||||
boolean inRain = entityData.get(DATA_IN_RAIN);
|
||||
boolean lowCeiling = entityData.get(DATA_UNDER_LOW_CEILING);
|
||||
boolean moving = entityData.get(DATA_MOVING);
|
||||
boolean eating = entityData.get(DATA_EATING);
|
||||
int fallTicks = entityData.get(DATA_FALL_TICKS);
|
||||
|
||||
if (sleeping) return RawAnimation.begin().thenLoop("sleep");
|
||||
if (fallTicks > 5) return RawAnimation.begin().thenPlay("fall");
|
||||
if (eating) return RawAnimation.begin().thenPlay("eat1");
|
||||
|
||||
if (tired) return moving
|
||||
? RawAnimation.begin().thenLoop("walkingsleepy")
|
||||
: RawAnimation.begin().thenLoop("idlesleepy");
|
||||
if (inRain) return moving
|
||||
? RawAnimation.begin().thenLoop("walkingrain")
|
||||
: RawAnimation.begin().thenLoop("idleconrain");
|
||||
if (lowCeiling) return moving
|
||||
? RawAnimation.begin().thenLoop("shiftwalking")
|
||||
: RawAnimation.begin().thenLoop("shiftidle");
|
||||
if (moving) return RawAnimation.begin().thenLoop("walkingsimple");
|
||||
return RawAnimation.begin().thenLoop("idleins");
|
||||
}
|
||||
|
||||
@Override
|
||||
public AgeableMob getBreedOffspring(ServerLevel level, AgeableMob other) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResult mobInteract(Player player, InteractionHand hand) {
|
||||
if (player.isShiftKeyDown()) return InteractionResult.PASS;
|
||||
|
||||
if (entityData.get(DATA_SLEEPING)) {
|
||||
if (!level().isClientSide) wakeUp();
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
|
||||
if (entityData.get(DATA_TIRED)) {
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
if (this.level().isClientSide) {
|
||||
ModNetworking.CHANNEL.sendToServer(new OpenDialogPacket(this.getId()));
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAdditionalSaveData(CompoundTag tag) {
|
||||
super.addAdditionalSaveData(tag);
|
||||
tag.putInt("TicksWithoutSleep", ticksWithoutSleep);
|
||||
tag.putFloat("Relationship", relationshipPoints);
|
||||
if (bedPos != null) tag.putLong("BedPos", bedPos.asLong());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readAdditionalSaveData(CompoundTag tag) {
|
||||
super.readAdditionalSaveData(tag);
|
||||
ticksWithoutSleep = tag.getInt("TicksWithoutSleep");
|
||||
relationshipPoints = tag.getFloat("Relationship");
|
||||
if (tag.contains("BedPos")) bedPos = BlockPos.of(tag.getLong("BedPos"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnimatableInstanceCache getAnimatableInstanceCache() { return cache; }
|
||||
|
||||
public void setSleeping(boolean s) { entityData.set(DATA_SLEEPING, s); }
|
||||
public boolean isSleeping() { return entityData.get(DATA_SLEEPING); }
|
||||
public boolean isTired() { return entityData.get(DATA_TIRED); }
|
||||
public boolean isInRain() { return entityData.get(DATA_IN_RAIN); }
|
||||
public boolean isUnderLowCeiling() { return entityData.get(DATA_UNDER_LOW_CEILING); }
|
||||
public BlockPos getBedPos() { return bedPos; }
|
||||
public float getRelationshipPoints() { return relationshipPoints; }
|
||||
public void addRelationshipPoints(float amount) {
|
||||
this.relationshipPoints = Math.min(100, Math.max(0, this.relationshipPoints + amount));
|
||||
}
|
||||
public int getTicksWithoutSleep() { return ticksWithoutSleep; }
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package me.sashegdev.fabled_hearts.entity.ellie;
|
||||
|
||||
import me.sashegdev.fabled_hearts.Main;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import software.bernie.geckolib.model.GeoModel;
|
||||
|
||||
public class EllieModel extends GeoModel<EllieEntity> {
|
||||
private static final ResourceLocation MODEL = new ResourceLocation(Main.MODID, "geo/ellie.geo.json");
|
||||
private static final ResourceLocation TEXTURE = new ResourceLocation(Main.MODID, "textures/entity/ellie.png");
|
||||
private static final ResourceLocation ANIMATIONS = new ResourceLocation(Main.MODID, "animations/ellie.animation.json");
|
||||
|
||||
@Override
|
||||
public ResourceLocation getModelResource(EllieEntity object) {
|
||||
return MODEL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getTextureResource(EllieEntity object) {
|
||||
return TEXTURE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getAnimationResource(EllieEntity animatable) {
|
||||
return ANIMATIONS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package me.sashegdev.fabled_hearts.entity.ellie;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.math.Axis;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.entity.EntityRendererProvider;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.Pose;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import software.bernie.geckolib.renderer.GeoEntityRenderer;
|
||||
|
||||
public class EllieRenderer extends GeoEntityRenderer<EllieEntity> {
|
||||
public EllieRenderer(EntityRendererProvider.Context renderManager) {
|
||||
super(renderManager, new EllieModel());
|
||||
this.shadowRadius = 0.5f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RenderType getRenderType(EllieEntity animatable, ResourceLocation texture,
|
||||
@Nullable MultiBufferSource bufferSource, float partialTick) {
|
||||
return RenderType.entityTranslucent(texture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(EllieEntity entity, float entityYaw, float partialTick, PoseStack poseStack,
|
||||
MultiBufferSource bufferSource, int packedLight) {
|
||||
if (entity.getPose() == Pose.SLEEPING) {
|
||||
poseStack.pushPose();
|
||||
poseStack.translate(0, 0.5625f, 0);
|
||||
poseStack.mulPose(Axis.XP.rotationDegrees(-90));
|
||||
super.render(entity, entityYaw, partialTick, poseStack, bufferSource, packedLight);
|
||||
poseStack.popPose();
|
||||
} else {
|
||||
super.render(entity, entityYaw, partialTick, poseStack, bufferSource, packedLight);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package me.sashegdev.fabled_hearts.entity.ellie;
|
||||
|
||||
import me.sashegdev.fabled_hearts.api.girl.INSFWAction;
|
||||
import me.sashegdev.fabled_hearts.data.RelationshipMilestones;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class NSFWHandler {
|
||||
private final EllieEntity ellie;
|
||||
private boolean inNSFWMode;
|
||||
private String currentPoseAnimation;
|
||||
|
||||
public NSFWHandler(EllieEntity ellie) {
|
||||
this.ellie = ellie;
|
||||
}
|
||||
|
||||
public boolean canStartNSFW(Player player) {
|
||||
return RelationshipMilestones.hasReached(ellie.getRelationshipPoints(), RelationshipMilestones.LOVE)
|
||||
&& !ellie.isTired()
|
||||
&& !ellie.isSleeping();
|
||||
}
|
||||
|
||||
public void startOral(Player player) {
|
||||
if (!canStartNSFW(player)) {
|
||||
player.sendSystemMessage(Component.literal("§cEllie не готова к этому..."));
|
||||
return;
|
||||
}
|
||||
inNSFWMode = true;
|
||||
currentPoseAnimation = "oral";
|
||||
player.sendSystemMessage(Component.literal("§5Ellie..."));
|
||||
}
|
||||
|
||||
public void choosePose(Player player, String poseAnimation) {
|
||||
if (!inNSFWMode) return;
|
||||
currentPoseAnimation = poseAnimation;
|
||||
player.sendSystemMessage(Component.literal("§5Смена позы..."));
|
||||
}
|
||||
|
||||
public void stopNSFW(Player player) {
|
||||
inNSFWMode = false;
|
||||
currentPoseAnimation = null;
|
||||
player.sendSystemMessage(Component.literal("§7Всё закончилось..."));
|
||||
}
|
||||
|
||||
public boolean isInNSFWMode() { return inNSFWMode; }
|
||||
public String getCurrentPoseAnimation() { return currentPoseAnimation; }
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package me.sashegdev.fabled_hearts.menu;
|
||||
|
||||
import me.sashegdev.fabled_hearts.Main;
|
||||
import me.sashegdev.fabled_hearts.data.WorldData;
|
||||
import me.sashegdev.fabled_hearts.registry.ModEntities;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResultHolder;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
public class EllieSpawnItem extends Item {
|
||||
public EllieSpawnItem() {
|
||||
super(new Item.Properties().stacksTo(1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand hand) {
|
||||
ItemStack stack = player.getItemInHand(hand);
|
||||
|
||||
if (!level.isClientSide) {
|
||||
ServerLevel serverLevel = (ServerLevel) level;
|
||||
WorldData data = WorldData.get(serverLevel);
|
||||
|
||||
if (data.hasEllie()) {
|
||||
Entity existing = serverLevel.getEntity(data.getEllieUUID());
|
||||
if (existing != null) {
|
||||
player.sendSystemMessage(Component.literal("§eEllie уже здесь!"));
|
||||
return InteractionResultHolder.success(stack);
|
||||
} else {
|
||||
data.removeEllie();
|
||||
}
|
||||
}
|
||||
|
||||
var entity = ModEntities.ELLIE.get().create(serverLevel);
|
||||
if (entity != null) {
|
||||
entity.setPos(player.getX(), player.getY(), player.getZ());
|
||||
serverLevel.addFreshEntity(entity);
|
||||
data.setEllieUUID(entity.getUUID());
|
||||
player.sendSystemMessage(Component.literal("§aEllie появилась!"));
|
||||
}
|
||||
}
|
||||
|
||||
return InteractionResultHolder.success(stack);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package me.sashegdev.fabled_hearts.network;
|
||||
|
||||
import me.sashegdev.fabled_hearts.dialog.DialogManager;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class DialogChoicePacket {
|
||||
private final int entityId;
|
||||
private final int choiceIndex;
|
||||
|
||||
public DialogChoicePacket(int entityId, int choiceIndex) {
|
||||
this.entityId = entityId;
|
||||
this.choiceIndex = choiceIndex;
|
||||
}
|
||||
|
||||
public void encode(FriendlyByteBuf buf) {
|
||||
buf.writeInt(entityId);
|
||||
buf.writeInt(choiceIndex);
|
||||
}
|
||||
|
||||
public static DialogChoicePacket decode(FriendlyByteBuf buf) {
|
||||
return new DialogChoicePacket(buf.readInt(), buf.readInt());
|
||||
}
|
||||
|
||||
public void handle(Supplier<NetworkEvent.Context> ctx) {
|
||||
ctx.get().enqueueWork(() -> {
|
||||
Player player = ctx.get().getSender();
|
||||
if (player != null && player.level().getEntity(entityId) instanceof me.sashegdev.fabled_hearts.entity.ellie.EllieEntity ellie) {
|
||||
DialogManager.get().makeChoice(ellie, player, choiceIndex);
|
||||
}
|
||||
});
|
||||
ctx.get().setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package me.sashegdev.fabled_hearts.network;
|
||||
|
||||
import me.sashegdev.fabled_hearts.dialog.DialogNode;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class DialogNodePacket {
|
||||
private final int entityId;
|
||||
private final String text;
|
||||
private final String animation;
|
||||
private final List<String> choiceTexts;
|
||||
|
||||
public DialogNodePacket(int entityId, DialogNode node) {
|
||||
this.entityId = entityId;
|
||||
this.text = node.getText();
|
||||
this.animation = node.getAnimation();
|
||||
this.choiceTexts = new ArrayList<>();
|
||||
for (var choice : node.getChoices()) {
|
||||
choiceTexts.add(choice.getText());
|
||||
}
|
||||
}
|
||||
|
||||
public DialogNodePacket(int entityId, String text, String animation, List<String> choiceTexts) {
|
||||
this.entityId = entityId;
|
||||
this.text = text;
|
||||
this.animation = animation;
|
||||
this.choiceTexts = choiceTexts;
|
||||
}
|
||||
|
||||
public void encode(FriendlyByteBuf buf) {
|
||||
buf.writeInt(entityId);
|
||||
buf.writeUtf(text);
|
||||
buf.writeUtf(animation);
|
||||
buf.writeInt(choiceTexts.size());
|
||||
for (var choice : choiceTexts) buf.writeUtf(choice);
|
||||
}
|
||||
|
||||
public static DialogNodePacket decode(FriendlyByteBuf buf) {
|
||||
int entityId = buf.readInt();
|
||||
String text = buf.readUtf();
|
||||
String animation = buf.readUtf();
|
||||
int size = buf.readInt();
|
||||
List<String> choices = new ArrayList<>();
|
||||
for (int i = 0; i < size; i++) choices.add(buf.readUtf());
|
||||
return new DialogNodePacket(entityId, text, animation, choices);
|
||||
}
|
||||
|
||||
public void handle(Supplier<NetworkEvent.Context> ctx) {
|
||||
ctx.get().enqueueWork(() -> {
|
||||
var minecraft = Minecraft.getInstance();
|
||||
var level = minecraft.level;
|
||||
if (level != null && level.getEntity(entityId) instanceof me.sashegdev.fabled_hearts.entity.ellie.EllieEntity ellie) {
|
||||
minecraft.setScreen(new me.sashegdev.fabled_hearts.dialog.DialogScreen(ellie, text, choiceTexts));
|
||||
}
|
||||
});
|
||||
ctx.get().setPacketHandled(true);
|
||||
}
|
||||
|
||||
public int getEntityId() { return entityId; }
|
||||
public String getText() { return text; }
|
||||
public String getAnimation() { return animation; }
|
||||
public List<String> getChoiceTexts() { return choiceTexts; }
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package me.sashegdev.fabled_hearts.network;
|
||||
|
||||
import me.sashegdev.fabled_hearts.Main;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.network.NetworkRegistry;
|
||||
import net.minecraftforge.network.simple.SimpleChannel;
|
||||
|
||||
public class ModNetworking {
|
||||
private static final String PROTOCOL = "1";
|
||||
public static final SimpleChannel CHANNEL = NetworkRegistry.newSimpleChannel(
|
||||
new ResourceLocation(Main.MODID, "main"),
|
||||
() -> PROTOCOL,
|
||||
PROTOCOL::equals,
|
||||
PROTOCOL::equals
|
||||
);
|
||||
|
||||
private static int id = 0;
|
||||
|
||||
public static void register() {
|
||||
CHANNEL.registerMessage(id++, OpenDialogPacket.class,
|
||||
OpenDialogPacket::encode, OpenDialogPacket::decode, OpenDialogPacket::handle);
|
||||
CHANNEL.registerMessage(id++, DialogChoicePacket.class,
|
||||
DialogChoicePacket::encode, DialogChoicePacket::decode, DialogChoicePacket::handle);
|
||||
CHANNEL.registerMessage(id++, DialogNodePacket.class,
|
||||
DialogNodePacket::encode, DialogNodePacket::decode, DialogNodePacket::handle);
|
||||
CHANNEL.registerMessage(id++, RelationshipSyncPacket.class,
|
||||
RelationshipSyncPacket::encode, RelationshipSyncPacket::decode, RelationshipSyncPacket::handle);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package me.sashegdev.fabled_hearts.network;
|
||||
|
||||
import me.sashegdev.fabled_hearts.dialog.DialogManager;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class OpenDialogPacket {
|
||||
private final int entityId;
|
||||
|
||||
public OpenDialogPacket(int entityId) {
|
||||
this.entityId = entityId;
|
||||
}
|
||||
|
||||
public void encode(FriendlyByteBuf buf) {
|
||||
buf.writeInt(entityId);
|
||||
}
|
||||
|
||||
public static OpenDialogPacket decode(FriendlyByteBuf buf) {
|
||||
return new OpenDialogPacket(buf.readInt());
|
||||
}
|
||||
|
||||
public void handle(Supplier<NetworkEvent.Context> ctx) {
|
||||
ctx.get().enqueueWork(() -> {
|
||||
Player player = ctx.get().getSender();
|
||||
if (player != null && player.level().getEntity(entityId) instanceof me.sashegdev.fabled_hearts.entity.ellie.EllieEntity ellie) {
|
||||
DialogManager.get().startDialog(ellie, player);
|
||||
}
|
||||
});
|
||||
ctx.get().setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package me.sashegdev.fabled_hearts.network;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class RelationshipSyncPacket {
|
||||
private final int entityId;
|
||||
private final float relationship;
|
||||
|
||||
public RelationshipSyncPacket(int entityId, float relationship) {
|
||||
this.entityId = entityId;
|
||||
this.relationship = relationship;
|
||||
}
|
||||
|
||||
public void encode(FriendlyByteBuf buf) {
|
||||
buf.writeInt(entityId);
|
||||
buf.writeFloat(relationship);
|
||||
}
|
||||
|
||||
public static RelationshipSyncPacket decode(FriendlyByteBuf buf) {
|
||||
return new RelationshipSyncPacket(buf.readInt(), buf.readFloat());
|
||||
}
|
||||
|
||||
public void handle(Supplier<NetworkEvent.Context> ctx) {
|
||||
ctx.get().enqueueWork(() -> {
|
||||
var level = Minecraft.getInstance().level;
|
||||
if (level != null && level.getEntity(entityId) instanceof me.sashegdev.fabled_hearts.entity.ellie.EllieEntity ellie) {
|
||||
ellie.addRelationshipPoints(relationship - ellie.getRelationshipPoints());
|
||||
}
|
||||
});
|
||||
ctx.get().setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package me.sashegdev.fabled_hearts.registry;
|
||||
|
||||
import me.sashegdev.fabled_hearts.Main;
|
||||
import me.sashegdev.fabled_hearts.entity.ellie.EllieEntity;
|
||||
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;
|
||||
|
||||
public class ModEntities {
|
||||
public static final DeferredRegister<EntityType<?>> ENTITIES =
|
||||
DeferredRegister.create(ForgeRegistries.ENTITY_TYPES, Main.MODID);
|
||||
|
||||
public static final RegistryObject<EntityType<EllieEntity>> ELLIE =
|
||||
ENTITIES.register("ellie", () -> EntityType.Builder.of(EllieEntity::new, MobCategory.CREATURE)
|
||||
.sized(0.9f, 2.5f)
|
||||
.clientTrackingRange(64)
|
||||
.build("ellie"));
|
||||
|
||||
public static void register(IEventBus bus) {
|
||||
ENTITIES.register(bus);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package me.sashegdev.fabled_hearts.registry;
|
||||
|
||||
import me.sashegdev.fabled_hearts.Main;
|
||||
import me.sashegdev.fabled_hearts.menu.EllieSpawnItem;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.item.CreativeModeTab;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.minecraftforge.registries.DeferredRegister;
|
||||
import net.minecraftforge.registries.ForgeRegistries;
|
||||
import net.minecraftforge.registries.RegistryObject;
|
||||
|
||||
public class ModItems {
|
||||
public static final DeferredRegister<Item> ITEMS =
|
||||
DeferredRegister.create(ForgeRegistries.ITEMS, Main.MODID);
|
||||
public static final DeferredRegister<CreativeModeTab> TABS =
|
||||
DeferredRegister.create(Registries.CREATIVE_MODE_TAB, Main.MODID);
|
||||
|
||||
public static final RegistryObject<Item> ELLIE_SPAWN = ITEMS.register("ellie_spawn", EllieSpawnItem::new);
|
||||
|
||||
public static final RegistryObject<CreativeModeTab> FABLED_TAB = TABS.register("fabled_hearts",
|
||||
() -> CreativeModeTab.builder()
|
||||
.icon(() -> new ItemStack(ELLIE_SPAWN.get()))
|
||||
.title(Component.literal("Fabled Hearts"))
|
||||
.displayItems((params, output) -> output.accept(ELLIE_SPAWN.get()))
|
||||
.build());
|
||||
|
||||
public static void register(IEventBus bus) {
|
||||
ITEMS.register(bus);
|
||||
TABS.register(bus);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
# 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 name 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 our 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
|
||||
# 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 name 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 = "bio.zernmc.ru" #optional
|
||||
# A file name (in the root of the mod JAR) containing a logo for display
|
||||
#logoFile="fabled_hearts.png" #optional
|
||||
# A text field displayed in the mod UI
|
||||
#credits="Thanks for this example mod goes to Java" #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" # MATCH_VERSION is the default if nothing is specified (#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 = "geckolib"
|
||||
mandatory = true
|
||||
versionRange = "[4.4,)"
|
||||
ordering = "NONE"
|
||||
side = "BOTH"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"dialogues": [
|
||||
{
|
||||
"id": "greeting",
|
||||
"text": "Привет, %player%! Рада тебя видеть!",
|
||||
"animation": "idleins",
|
||||
"choices": [
|
||||
{
|
||||
"text": "Привет! Как дела?",
|
||||
"next": "how_are_you",
|
||||
"conditions": [],
|
||||
"effects": [
|
||||
{ "type": "add_relationship", "value": 2 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "...",
|
||||
"next": "ignore",
|
||||
"conditions": [],
|
||||
"effects": [
|
||||
{ "type": "add_relationship", "value": -1 }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "how_are_you",
|
||||
"text": "Всё хорошо! Спасибо, что спросил!",
|
||||
"animation": "idleins",
|
||||
"choices": [
|
||||
{
|
||||
"text": "Я принёс тебе подарок",
|
||||
"next": "gift",
|
||||
"conditions": [],
|
||||
"effects": []
|
||||
},
|
||||
{
|
||||
"text": "Пока!",
|
||||
"next": "__end__",
|
||||
"conditions": [],
|
||||
"effects": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ignore",
|
||||
"text": "...(грустно молчит)",
|
||||
"animation": "idleins",
|
||||
"choices": [
|
||||
{
|
||||
"text": "Ладно, пока",
|
||||
"next": "__end__",
|
||||
"conditions": [],
|
||||
"effects": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gift",
|
||||
"text": "Ой, правда? Мне очень приятно!",
|
||||
"animation": "idleins",
|
||||
"choices": [
|
||||
{
|
||||
"text": "Носи на здоровье!",
|
||||
"next": "__end__",
|
||||
"conditions": [],
|
||||
"effects": [
|
||||
{ "type": "add_relationship", "value": 10 }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"pack": {
|
||||
"description": "fabled_hearts resources",
|
||||
"pack_format": 15
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user