commit b83441e76299f3095974090de4b6dd532f8a915e Author: 3944Realms Date: Thu May 29 17:54:41 2025 +0800 完成基本的迁移工作,能正常运行,并和迁移前功能一致 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d5f737e --- /dev/null +++ b/.gitignore @@ -0,0 +1,119 @@ +# 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/ + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar diff --git a/README.md b/README.md new file mode 100644 index 0000000..3f6898c --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +Migrate from [BlastTravel Quilt 1.20.1](https://github.com/Abbie5/BlastTravel) \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..c36b8e9 --- /dev/null +++ b/build.gradle @@ -0,0 +1,226 @@ +buildscript { + repositories { + // These repositories are only for Gradle plugins, put any other repositories in the repository block further below + maven { url = 'https://repo.spongepowered.org/repository/maven-public/' } + mavenCentral() + } +} + +plugins { + id 'eclipse' + id 'idea' + id 'net.minecraftforge.gradle' version '[6.0.16,6.2)' + id 'org.parchmentmc.librarian.forgegradle' version '1.+' + id 'org.spongepowered.mixin' version '0.7.+' +} + +apply plugin: 'org.spongepowered.mixin' + +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') + accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') + // 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 + args "-mixin.config="+mod_id+".mixins.json" + } + + server { + property 'forge.enabledGameTestNamespaces', mod_id + args '--nogui', "-mixin.config="+mod_id+".mixins.json" + } + + // 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 + args '--nogui', "-mixin.config="+mod_id+".mixins.json" + } + + 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/'), "-mixin.config="+mod_id+".mixins.json" + } + } +} + +mixin { + add sourceSets.main, "${mod_id}.refmap.json" + + config "${mod_id}.mixins.json" + debug.export = true +} + +// Include resources generated by data generators. +sourceSets.main.resources { srcDir 'src/generated/resources' } +processResources { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE +} +repositories { + // Put repositories for dependencies here + // ForgeGradle automatically adds the Forge maven and Maven Central for you + maven { + url = 'https://www.cursemaven.com' + content { + includeGroup 'curse.maven' + } + } + maven { + url = "https://api.modrinth.com/maven" + content { + includeGroup("maven.modrinth") + } + } + maven { + name = "Fuzs Mod Resources" + url = "https://raw.githubusercontent.com/Fuzss/modresources/main/maven/" + } + // If you have mod jar dependencies in ./libs, you can declare them as a repository like so. + // See https://docs.gradle.org/current/userguide/declaring_repositories.html#sub:flat_dir_resolver + flatDir { + dir 'libs' + } + +} + +dependencies { + // Specify the version of Minecraft to use. + // Any artifact can be supplied so long as it has a "userdev" classifier artifact and is a compatible patcher artifact. + // The "userdev" classifier will be requested and setup by ForgeGradle. + // If the group id is "net.minecraft" and the artifact id is one of ["client", "server", "joined"], + // then special handling is done to allow a setup of a vanilla dependency without the use of an external repository. + minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}" + implementation 'org.spongepowered:mixin:0.8.5' + annotationProcessor 'org.spongepowered:mixin:0.8.5:processor' + implementation fg.deobf("blank:jsonem-${minecraft_version}:${jsonem_version}") + // Example mod dependency with JEI - using fg.deobf() ensures the dependency is remapped to your development mappings + // The JEI API is declared for compile time use, while the full JEI artifact is used at runtime + // compileOnly fg.deobf("mezz.jei:jei-${mc_version}-common-api:${jei_version}") + // compileOnly fg.deobf("mezz.jei:jei-${mc_version}-forge-api:${jei_version}") + // runtimeOnly fg.deobf("mezz.jei:jei-${mc_version}-forge:${jei_version}") + + // Example mod dependency using a mod jar from ./libs with a flat dir repository + // This maps to ./libs/coolmod-${mc_version}-${coolmod_version}.jar + // The group id is ignored when searching -- in this case, it is "blank" + // implementation fg.deobf("blank:coolmod-${mc_version}:${coolmod_version}") + + // For more info: + // http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html + // http://www.gradle.org/docs/current/userguide/dependency_management.html + + annotationProcessor 'org.spongepowered:mixin:0.8.5:processor' + +} + +// 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 +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..aab887d --- /dev/null +++ b/gradle.properties @@ -0,0 +1,50 @@ +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.1 +# 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=parchment +# The mapping version to query from the mapping channel. +# This must match the format required by the mapping channel. +mapping_version=2023.09.03-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=blasttravelreborn +# The human-readable display name for the mod. +mod_name=BlastTravel-Reborn +# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. +mod_license=MIT +# The mod version. See https://semver.org/ +mod_version=1.0-SNAPSHOT +jsonem_version=0.2.1+1.20-fabrge +# 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=com.leisuretimedock.blasttravelreborn +# The authors of the mod. This is a simple text string that is used for display purposes in the mod list. +mod_authors=FoundationGames(Original Author), Abbie5(Migrate into Quilt 1.20.1), R3944Realms +# The description of the mod. This is a simple multiline text string that is used for display purposes in the mod list. +mod_description=Migrate from Quilt 1.20.1,1.19.2 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e644113 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3fdd479 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.8-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..b740cf1 --- /dev/null +++ b/gradlew @@ -0,0 +1,249 @@ +#!/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. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${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 + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..25da30d --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@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 %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/img/blast_travel.png b/img/blast_travel.png new file mode 100644 index 0000000..e6ea532 Binary files /dev/null and b/img/blast_travel.png differ diff --git a/img/cannon_ui.png b/img/cannon_ui.png new file mode 100644 index 0000000..cc460e8 Binary files /dev/null and b/img/cannon_ui.png differ diff --git a/img/in_cannon.png b/img/in_cannon.png new file mode 100644 index 0000000..4006df0 Binary files /dev/null and b/img/in_cannon.png differ diff --git a/img/recipe.png b/img/recipe.png new file mode 100644 index 0000000..c58ac4a Binary files /dev/null and b/img/recipe.png differ diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..f1cad22 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,18 @@ +pluginManagement { + repositories { + gradlePluginPortal() + maven { + name = 'MinecraftForge' + url = 'https://maven.minecraftforge.net/' + } + repositories { + maven { url = 'https://maven.parchmentmc.org' } // Add this line + } + } +} + +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0' +} + +rootProject.name = 'blasttravel-reborn' diff --git a/src/main/java/com/leisuretimedock/blasttravelreborn/BlastTravelReborn.java b/src/main/java/com/leisuretimedock/blasttravelreborn/BlastTravelReborn.java new file mode 100644 index 0000000..cf69175 --- /dev/null +++ b/src/main/java/com/leisuretimedock/blasttravelreborn/BlastTravelReborn.java @@ -0,0 +1,30 @@ +package com.leisuretimedock.blasttravelreborn; + +import com.leisuretimedock.blasttravelreborn.content.BTRParticleTypes; +import com.leisuretimedock.blasttravelreborn.content.entity.BTREntityTypes; +import com.leisuretimedock.blasttravelreborn.content.item.BTRItems; +import com.leisuretimedock.blasttravelreborn.content.menu.BTRMenuTypes; +import com.leisuretimedock.blasttravelreborn.network.BTRNetwork; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +@Mod(value = BlastTravelReborn.MOD_ID) +public class BlastTravelReborn { + public static final String MOD_ID = "blasttravelreborn"; + public static final Logger LOG = LoggerFactory.getLogger("Blast Travel Reborn"); + public BlastTravelReborn(FMLJavaModLoadingContext fmlJavaModLoadingContext) { + IEventBus modEventBus = fmlJavaModLoadingContext.getModEventBus(); + BTRNetwork.register(); + BTRItems.register(modEventBus); + BTRParticleTypes.register(modEventBus); + BTREntityTypes.register(modEventBus); + BTRMenuTypes.register(modEventBus); + } + + public static ResourceLocation id(String path) { + return ResourceLocation.fromNamespaceAndPath(MOD_ID, path); + } +} diff --git a/src/main/java/com/leisuretimedock/blasttravelreborn/client/BlastTravelClient.java b/src/main/java/com/leisuretimedock/blasttravelreborn/client/BlastTravelClient.java new file mode 100644 index 0000000..bdf834c --- /dev/null +++ b/src/main/java/com/leisuretimedock/blasttravelreborn/client/BlastTravelClient.java @@ -0,0 +1,55 @@ +package com.leisuretimedock.blasttravelreborn.client; + +import com.leisuretimedock.blasttravelreborn.BlastTravelReborn; +import com.leisuretimedock.blasttravelreborn.client.entity.CannonEntityRenderer; +import com.leisuretimedock.blasttravelreborn.client.particle.CannonBlastParticle; +import com.leisuretimedock.blasttravelreborn.client.screen.CannonScreen; +import com.leisuretimedock.blasttravelreborn.client.screen.overlay.CannonOverlay; +import com.leisuretimedock.blasttravelreborn.content.BTRParticleTypes; +import com.leisuretimedock.blasttravelreborn.content.entity.BTREntityTypes; +import com.leisuretimedock.blasttravelreborn.content.menu.BTRMenuTypes; +import com.leisuretimedock.blasttravelreborn.util.BTRUtil; +import com.leisuretimedock.jsonem.JsonEm; +import net.minecraft.client.gui.screens.MenuScreens; +import net.minecraft.util.Mth; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.client.event.EntityRenderersEvent; +import net.minecraftforge.client.event.RegisterGuiOverlaysEvent; +import net.minecraftforge.client.event.RegisterParticleProvidersEvent; +import net.minecraftforge.client.gui.overlay.VanillaGuiOverlay; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; +import net.minecraftforge.fml.event.lifecycle.FMLConstructModEvent; + +@Mod.EventBusSubscriber(modid = BlastTravelReborn.MOD_ID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD) +public class BlastTravelClient { + + @SubscribeEvent + public static void onClientInitPreInit(final FMLConstructModEvent event) { + onInitializeClient(); + } + @SubscribeEvent + public static void onFmlClientSetup(final FMLClientSetupEvent event) { + event.enqueueWork(() -> MenuScreens.register(BTRMenuTypes.CANNON_CONTAINER_MENU.get(), CannonScreen::new)); + } + @SubscribeEvent + public static void onEntityRenderRegistry(final EntityRenderersEvent.RegisterRenderers event) { + event.registerEntityRenderer(BTREntityTypes.CANNON_ENTITY.get(), CannonEntityRenderer::new); + } + @SubscribeEvent + public static void onRegisterParticleProviders(RegisterParticleProvidersEvent event) { + event.registerSpriteSet(BTRParticleTypes.CANNON_BLAST.get(), CannonBlastParticle.Provider::new); + } + + @SubscribeEvent + public static void onOverlayRegister(RegisterGuiOverlaysEvent event) { + event.registerAbove(VanillaGuiOverlay.SPYGLASS.id(), "cannon", + new CannonOverlay() + ); + } + + public static void onInitializeClient() { + JsonEm.registerModelLayer(CannonEntityRenderer.MODEL); + } +} diff --git a/src/main/java/com/leisuretimedock/blasttravelreborn/client/entity/CannonEntityRenderer.java b/src/main/java/com/leisuretimedock/blasttravelreborn/client/entity/CannonEntityRenderer.java new file mode 100644 index 0000000..58ffe18 --- /dev/null +++ b/src/main/java/com/leisuretimedock/blasttravelreborn/client/entity/CannonEntityRenderer.java @@ -0,0 +1,154 @@ +package com.leisuretimedock.blasttravelreborn.client.entity; + +import com.leisuretimedock.blasttravelreborn.BlastTravelReborn; +import com.leisuretimedock.blasttravelreborn.content.entity.CannonEntity; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.math.Axis; +import net.minecraft.client.Minecraft; +import net.minecraft.client.model.geom.ModelLayerLocation; +import net.minecraft.client.model.geom.ModelPart; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.entity.EntityRenderer; +import net.minecraft.client.renderer.entity.EntityRendererProvider; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import org.jetbrains.annotations.NotNull; +@OnlyIn(Dist.CLIENT) +public class CannonEntityRenderer extends EntityRenderer { + public static final ModelLayerLocation MODEL = new ModelLayerLocation(BlastTravelReborn.id("cannon"), "main"); + + private static final ResourceLocation[] FIRE_TEXTURES = { + BlastTravelReborn.id("textures/entity/cannon_fire/frame_0.png"), + BlastTravelReborn.id("textures/entity/cannon_fire/frame_1.png"), + BlastTravelReborn.id("textures/entity/cannon_fire/frame_2.png"), + BlastTravelReborn.id("textures/entity/cannon_fire/frame_3.png"), + BlastTravelReborn.id("textures/entity/cannon_fire/frame_4.png") + }; + + private static final ResourceLocation[] DYED_FIRE_TEXTURES = { + BlastTravelReborn.id("textures/entity/cannon_fire/dyed_frame_0.png"), + BlastTravelReborn.id("textures/entity/cannon_fire/dyed_frame_1.png"), + BlastTravelReborn.id("textures/entity/cannon_fire/dyed_frame_2.png"), + BlastTravelReborn.id("textures/entity/cannon_fire/dyed_frame_3.png"), + BlastTravelReborn.id("textures/entity/cannon_fire/dyed_frame_4.png") + }; + + private static final ResourceLocation[] SMOKE_TEXTURES = { + BlastTravelReborn.id("textures/entity/cannon_smoke/frame_0.png"), + BlastTravelReborn.id("textures/entity/cannon_smoke/frame_1.png"), + BlastTravelReborn.id("textures/entity/cannon_smoke/frame_2.png"), + BlastTravelReborn.id("textures/entity/cannon_smoke/frame_3.png"), + BlastTravelReborn.id("textures/entity/cannon_smoke/frame_4.png") + }; + + private final ModelPart root; + private final ModelPart leftWheel; + private final ModelPart rightWheel; + private final ModelPart cannon; + private final ModelPart chains; + private final ModelPart fuse; + private final ModelPart playerHead; + private final ModelPart fire; + + public CannonEntityRenderer(EntityRendererProvider.Context context) { + super(context); + + this.root = context.bakeLayer(MODEL).getChild("main"); + + this.leftWheel = this.root.getChild("left_wheel"); + this.rightWheel = this.root.getChild("right_wheel"); + this.cannon = this.root.getChild("cannon"); + this.playerHead = this.root.getChild("player_head"); + this.fire = this.root.getChild("fire"); + + this.chains = this.cannon.getChild("chains"); + this.fuse = this.cannon.getChild("fuse"); + + this.resetModel(); + } + + private void resetModel() { + this.leftWheel.xRot = this.rightWheel.xRot = 0; + this.cannon.xRot = 0; + + this.cannon.visible = true; + this.playerHead.visible = false; + this.fuse.visible = true; + this.chains.visible = false; + this.fire.visible = false; + } + + @Override + public @NotNull ResourceLocation getTextureLocation(CannonEntity entity) { + return entity.getBehavior().texture(entity.getBehaviorStack()); + } + + @Override + public void render(@NotNull CannonEntity entity, float yaw, float tickDelta, @NotNull PoseStack matrices, @NotNull MultiBufferSource vertexConsumers, int light) { + super.render(entity, yaw, tickDelta, matrices, vertexConsumers, light); + matrices.pushPose(); + var behavior = entity.getBehavior(); + + yaw = (180 + yaw) * Mth.DEG_TO_RAD; + + // 1.2 is the ratio of the circumference of the wheelbase to the circumference of the wheel (probably) + float wheelAngle = yaw * 1.2f; + + this.leftWheel.xRot = wheelAngle; + this.rightWheel.xRot = -wheelAngle; + this.cannon.xRot = (entity.getViewXRot(tickDelta) + 90) * Mth.DEG_TO_RAD; + + boolean renderCannon = (entity.getFirstPassenger() != Minecraft.getInstance().player) || + Minecraft.getInstance().gameRenderer.getMainCamera().isDetached(); + + this.cannon.visible = renderCannon; + this.chains.visible = entity.hasChains(); + this.fuse.visible = entity.hasFuse(); + + matrices.mulPose(Axis.ZP.rotationDegrees(180)); + matrices.mulPose(Axis.YP.rotation(yaw)); + + float anim = entity.getAnimation(tickDelta); + matrices.mulPose(Axis.XP.rotationDegrees( + -5 * (-2 * (anim*anim*anim*anim*anim*anim*anim*anim) + 2 * (anim*anim)))); // pow() goes the cannon + + this.root.render(matrices, vertexConsumers.getBuffer(RenderType.entityCutout(this.getTextureLocation(entity))), light, OverlayTexture.NO_OVERLAY); + + var color = behavior.fireColor(entity); + int tick = Math.max(0, (CannonEntity.MAX_ANIMATION - entity.getAnimationTick()) - 3); + if (tick <= 7) { + this.fire.visible = true; + this.fire.xRot = this.cannon.xRot; + } + if (tick <= 4) { + if (color != null) { + this.fire.render(matrices, vertexConsumers.getBuffer(RenderType.eyes(DYED_FIRE_TEXTURES[tick])), + light, OverlayTexture.NO_OVERLAY, color.x, color.y, color.z, 1); + } else { + this.fire.render(matrices, vertexConsumers.getBuffer(RenderType.eyes(FIRE_TEXTURES[tick])), light, OverlayTexture.NO_OVERLAY); + } + } + tick -= 3; + if (tick >= 0 && tick <= 4) { + this.fire.render(matrices, vertexConsumers.getBuffer(RenderType.entityTranslucent(SMOKE_TEXTURES[tick])), light, OverlayTexture.NO_OVERLAY, + 1, 1, 1, 0.9f - (0.07f * tick)); + } + + if (renderCannon && behavior.displayHead(entity)) { + this.playerHead.visible = true; + this.playerHead.xRot = this.cannon.xRot; + + color = behavior.headColor(entity); + this.playerHead.render(matrices, + vertexConsumers.getBuffer(RenderType.entityCutoutNoCull(behavior.headTexture(entity))), + light, OverlayTexture.NO_OVERLAY, color.x, color.y, color.z, 1); + } + + this.resetModel(); + matrices.popPose(); + } +} diff --git a/src/main/java/com/leisuretimedock/blasttravelreborn/client/particle/CannonBlastParticle.java b/src/main/java/com/leisuretimedock/blasttravelreborn/client/particle/CannonBlastParticle.java new file mode 100644 index 0000000..c9f75e9 --- /dev/null +++ b/src/main/java/com/leisuretimedock/blasttravelreborn/client/particle/CannonBlastParticle.java @@ -0,0 +1,45 @@ +package com.leisuretimedock.blasttravelreborn.client.particle; + +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.particle.*; +import net.minecraft.core.particles.SimpleParticleType; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import org.jetbrains.annotations.NotNull; +@OnlyIn(Dist.CLIENT) +public class CannonBlastParticle extends RisingParticle { + private final SpriteSet sprites; + + public CannonBlastParticle(ClientLevel world, double x, double y, double z, double vx, double vy, double vz, SpriteSet sprites) { + super(world, x, y, z, vx, vy, vz); + this.sprites = sprites; + this.age = world.random.nextInt(2); + this.lifetime = 16; + this.friction = 0.76f; + this.scale(1.5f + world.random.nextFloat() * 0.2f); + this.setSpriteFromAge(sprites); + } + + @Override + public void tick() { + super.tick(); + this.setSpriteFromAge(this.sprites); + } + + @Override + public @NotNull ParticleRenderType getRenderType() { + return ParticleRenderType.PARTICLE_SHEET_OPAQUE; + } + + public static class Provider implements ParticleProvider { + private final SpriteSet sprites; + + public Provider(SpriteSet sprites) { + this.sprites = sprites; + } + + public Particle createParticle(@NotNull SimpleParticleType type, @NotNull ClientLevel world, double x, double y, double z, double vx, double vy, double vz) { + return new CannonBlastParticle(world, x, y, z, vx, vy, vz, this.sprites); + } + } +} diff --git a/src/main/java/com/leisuretimedock/blasttravelreborn/client/screen/CannonScreen.java b/src/main/java/com/leisuretimedock/blasttravelreborn/client/screen/CannonScreen.java new file mode 100644 index 0000000..72a230c --- /dev/null +++ b/src/main/java/com/leisuretimedock/blasttravelreborn/client/screen/CannonScreen.java @@ -0,0 +1,69 @@ +package com.leisuretimedock.blasttravelreborn.client.screen; + +import com.leisuretimedock.blasttravelreborn.BlastTravelReborn; +import com.leisuretimedock.blasttravelreborn.content.entity.cannon.CannonBehavior; +import com.leisuretimedock.blasttravelreborn.content.menu.CannonContainerMenu; +import com.mojang.blaze3d.systems.RenderSystem; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import org.jetbrains.annotations.NotNull; +@OnlyIn(Dist.CLIENT) +public class CannonScreen extends AbstractContainerScreen { + private static final ResourceLocation TEXTURE = BlastTravelReborn.id("textures/gui/container/cannon.png"); + + private final ItemStack[] allowedBehaviorStacks = CannonBehavior.allBehaviors().stream() + .filter(b -> b.icon != Items.AIR).map(b -> new ItemStack(b.icon)).toArray(ItemStack[]::new); + + private long time = (long) (Math.random() * 1000); + + public CannonScreen(CannonContainerMenu handler, Inventory inventory, Component title) { + super(handler, inventory, title); + + this.imageHeight = 140; + this.inventoryLabelY = 47; + this.titleLabelY = 8; + this.titleLabelX = 61; + } + + @Override + public void render(@NotNull GuiGraphics gui, int mouseX, int mouseY, float delta) { + super.render(gui, mouseX, mouseY, delta); + this.renderTooltip(gui, mouseX, mouseY); + } + + @Override + protected void renderBg(@NotNull GuiGraphics gui, float delta, int mouseX, int mouseY) { + this.renderBackground(gui); + + gui.blit(TEXTURE, this.leftPos, this.topPos, 0, 0, this.imageWidth, this.imageHeight); + + if (this.getMenu().inventory.getItem(2).isEmpty()) { + int x = this.leftPos + 98; int y = this.topPos + 20; + gui.renderItem( + this.allowedBehaviorStacks[Mth.floor((float)this.time / 30) % this.allowedBehaviorStacks.length], x, y); + + RenderSystem.depthFunc(516); + gui.fill(x, y, x + 16, y + 16, 0x8B8B8B8B); + RenderSystem.depthFunc(515); + } + + for (int i = 0; i < 2; i++) { + if (this.getMenu().inventory.getItem(i).isEmpty()) { + gui.blit(TEXTURE, this.leftPos + 62 + (18 * i), this.topPos + 20, 16 * i, 140, 16, 16); + } + } + } + + @Override + protected void containerTick() { + this.time++; + } +} diff --git a/src/main/java/com/leisuretimedock/blasttravelreborn/client/screen/overlay/CannonOverlay.java b/src/main/java/com/leisuretimedock/blasttravelreborn/client/screen/overlay/CannonOverlay.java new file mode 100644 index 0000000..5077187 --- /dev/null +++ b/src/main/java/com/leisuretimedock/blasttravelreborn/client/screen/overlay/CannonOverlay.java @@ -0,0 +1,26 @@ +package com.leisuretimedock.blasttravelreborn.client.screen.overlay; + +import com.leisuretimedock.blasttravelreborn.content.entity.CannonEntity; +import com.leisuretimedock.blasttravelreborn.util.BTRUtil; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.util.Mth; +import net.minecraftforge.client.gui.overlay.ForgeGui; +import net.minecraftforge.client.gui.overlay.IGuiOverlay; + +public class CannonOverlay implements IGuiOverlay { + private float cannonOverlayScale = 0.5f; + private final Minecraft mc; + public CannonOverlay() { + this.mc = Minecraft.getInstance(); + } + @Override + public void render(ForgeGui gui, GuiGraphics guiGraphics, float partialTick, int screenWidth, int screenHeight) { + if(mc.player != null && mc.player.getVehicle() instanceof CannonEntity){ + cannonOverlayScale = Mth.lerp(0.5f * partialTick, cannonOverlayScale, 1.125f); + if(mc.options.getCameraType().isFirstPerson()){ + BTRUtil.renderCannonHubOverlay(guiGraphics, cannonOverlayScale); + } + } + } +} diff --git a/src/main/java/com/leisuretimedock/blasttravelreborn/content/BTRParticleTypes.java b/src/main/java/com/leisuretimedock/blasttravelreborn/content/BTRParticleTypes.java new file mode 100644 index 0000000..fbb5370 --- /dev/null +++ b/src/main/java/com/leisuretimedock/blasttravelreborn/content/BTRParticleTypes.java @@ -0,0 +1,21 @@ +package com.leisuretimedock.blasttravelreborn.content; + +import com.leisuretimedock.blasttravelreborn.BlastTravelReborn; +import net.minecraft.core.particles.ParticleType; +import net.minecraft.core.particles.SimpleParticleType; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegistryObject; + +public class BTRParticleTypes { + public static final DeferredRegister> PARTICLE_TYPES = DeferredRegister.create(ForgeRegistries.PARTICLE_TYPES, BlastTravelReborn.MOD_ID); + public static final RegistryObject CANNON_BLAST = + PARTICLE_TYPES.register( + "cannon_blast", + () -> new SimpleParticleType(true) + ); + public static void register(IEventBus eventBus) { + PARTICLE_TYPES.register(eventBus); + } +} diff --git a/src/main/java/com/leisuretimedock/blasttravelreborn/content/entity/BTREntityTypes.java b/src/main/java/com/leisuretimedock/blasttravelreborn/content/entity/BTREntityTypes.java new file mode 100644 index 0000000..d1c42b1 --- /dev/null +++ b/src/main/java/com/leisuretimedock/blasttravelreborn/content/entity/BTREntityTypes.java @@ -0,0 +1,24 @@ +package com.leisuretimedock.blasttravelreborn.content.entity; + +import com.leisuretimedock.blasttravelreborn.BlastTravelReborn; +import net.minecraft.resources.ResourceLocation; +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 BTREntityTypes { + public static final DeferredRegister> ENTITY_TYPES = DeferredRegister.create(ForgeRegistries.ENTITY_TYPES, BlastTravelReborn.MOD_ID); + public static final RegistryObject> CANNON_ENTITY = + ENTITY_TYPES.register( + "cannon", + () -> EntityType.Builder.of(CannonEntity::new, MobCategory.MISC) + .sized(1, 0.8f) + .build((ResourceLocation.fromNamespaceAndPath(BlastTravelReborn.MOD_ID, "cannon")).toString() + )); + public static void register(IEventBus eventBus) { + ENTITY_TYPES.register(eventBus); + } +} diff --git a/src/main/java/com/leisuretimedock/blasttravelreborn/content/entity/CannonEntity.java b/src/main/java/com/leisuretimedock/blasttravelreborn/content/entity/CannonEntity.java new file mode 100644 index 0000000..dae8dac --- /dev/null +++ b/src/main/java/com/leisuretimedock/blasttravelreborn/content/entity/CannonEntity.java @@ -0,0 +1,452 @@ +package com.leisuretimedock.blasttravelreborn.content.entity; + +import com.leisuretimedock.blasttravelreborn.BlastTravelReborn; +import com.leisuretimedock.blasttravelreborn.content.BTRParticleTypes; +import com.leisuretimedock.blasttravelreborn.content.entity.cannon.CannonBehavior; +import com.leisuretimedock.blasttravelreborn.content.entity.cannon.ConcretePowderCannonBehavior; +import com.leisuretimedock.blasttravelreborn.content.entity.cannon.EntityCannonBehavior; +import com.leisuretimedock.blasttravelreborn.content.item.BTRItems; +import com.leisuretimedock.blasttravelreborn.content.menu.CannonContainerMenu; +import com.leisuretimedock.blasttravelreborn.network.BTRNetwork; +import com.leisuretimedock.blasttravelreborn.network.toClient.FireCannonPayload; +import com.leisuretimedock.blasttravelreborn.network.toServer.RequestFirePayload; +import com.leisuretimedock.blasttravelreborn.util.PlayerEntityDuck; +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.core.particles.ParticleTypes; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket; +import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.network.syncher.EntityDataSerializers; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.tags.ItemTags; +import net.minecraft.util.Mth; +import net.minecraft.world.*; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.MoverType; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.PickaxeItem; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.network.NetworkDirection; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +@SuppressWarnings("unused") +public class CannonEntity extends Entity { + public static final Component UI_TITLE = Component.translatable("container.blasttravelreborn.cannon_container_menu"); + public static final Component NO_GUNPOWDER_DIALOG = Component.translatable("dialog.blasttravelreborn.no_gunpowder").withStyle(ChatFormatting.RED); + public static final Component FULL_CANNON_DIALOG = Component.translatable("dialog.blasttravelreborn.full_cannon").withStyle(ChatFormatting.RED); + + public static final CannonBehavior NONE = new CannonBehavior(Items.AIR, stack -> false).register(); + public static final CannonBehavior GOLDEN = new CannonBehavior(Items.GOLD_BLOCK, BlastTravelReborn.id("textures/entity/cannon/golden.png")).register(); + public static final CannonBehavior MOSSY = new CannonBehavior(Items.MOSS_BLOCK, BlastTravelReborn.id("textures/entity/cannon/mossy.png")).register(); + public static final CannonBehavior LAZULI = new CannonBehavior(Items.LAPIS_BLOCK, BlastTravelReborn.id("textures/entity/cannon/lazuli.png")).register(); + public static final CannonBehavior AMETHYST = new CannonBehavior(Items.AMETHYST_BLOCK, BlastTravelReborn.id("textures/entity/cannon/amethyst.png")).register(); + public static final CannonBehavior TNT = new EntityCannonBehavior(Items.TNT, BlastTravelReborn.id("textures/entity/cannon/tnt.png"), EntityCannonBehavior::tntFactory).register(); + public static final CannonBehavior ANVIL = new EntityCannonBehavior(Items.ANVIL, s -> s.is(ItemTags.ANVIL), BlastTravelReborn.id("textures/entity/cannon/anvil.png"), EntityCannonBehavior::fallingBlockFactory).register(); + public static final CannonBehavior POWDER = new ConcretePowderCannonBehavior().register(); + + public static final EntityDataAccessor BEHAVIOR = SynchedEntityData.defineId(CannonEntity.class, EntityDataSerializers.INT); + public static final EntityDataAccessor CHAINED = SynchedEntityData.defineId(CannonEntity.class, EntityDataSerializers.BOOLEAN); + public static final EntityDataAccessor BEHAVIOR_STACK = SynchedEntityData.defineId(CannonEntity.class, EntityDataSerializers.ITEM_STACK); + + public static final int MAX_ANIMATION = 12; + + private boolean chained; + private boolean firing; + private boolean powered; + private boolean alwaysModifiable; + + private int animation = 0; + + private double targetX; + private double targetY; + private double targetZ; + private int targetTicks; + + private final SimpleContainer inventory = new SimpleContainer(3) { + @Override + public void setItem(int slot, @NotNull ItemStack stack) { + super.setItem(slot, stack); + CannonEntity.this.updateStateFromInventory(); + } + }; + + public CannonEntity(EntityType type, Level world) { + super(type, world); + } + + public CannonEntity(Level world) { + this(BTREntityTypes.CANNON_ENTITY.get(), world); + } + + @Override + public void tick() { + if (this.firing && !this.isVehicle()) { + this.firing = false; + } + + super.tick(); + + if (!this.chained && this.getFirstPassenger() instanceof Player player) { + this.setYRot(player.getYHeadRot()); + this.setXRot(player.getXRot()); + } + + if (this.entityData.get(CHAINED) != this.chained) { + if (!level().isClientSide()) { + this.entityData.set(CHAINED, this.chained); + } else { + setChained(this.entityData.get(CHAINED)); + } + } + + if (this.animation > 0) { + this.animation--; + } + + if (this.level().isClientSide()) { + if (this.hasFuse()) { + var pos = this.position().add(0, 0.75, 0).add(this.calculateViewVector(this.getXRot() - 90, this.getYRot()).scale(0.75)); + Minecraft.getInstance().particleEngine.createParticle(ParticleTypes.SMOKE, + pos.x, pos.y, pos.z, 0, 0, 0); + } + + this.positionTrackTick(); + } else { + boolean hasPower = this.level().getBestNeighborSignal(this.blockPosition()) > 0 || + this.level().getBestNeighborSignal(this.blockPosition().below()) > 0; + if (hasPower != this.powered) { + if (hasPower) { + this.fireServer(); + } + this.powered = hasPower; + } + + this.movementTick(); + this.move(MoverType.SELF, this.getDeltaMovement()); + } + } + + @Override + public void onPassengerTurned(@NotNull Entity passenger) { + if (this.level().isClientSide() && passenger instanceof Player player && player.isLocalPlayer()) { + if (chained) { + player.setYRot(this.getYRot()); + player.setXRot(this.getXRot()); + } else { + player.setXRot(Math.min(18, player.getXRot())); + } + } + } + + public boolean canPlayerModify(Player player) { + return this.alwaysModifiable || player.mayBuild(); + } + + @Override + public @NotNull InteractionResult interact(@NotNull Player player, @NotNull InteractionHand hand) { + if (player == this.getFirstPassenger()) { + return super.interact(player, hand); + } + + if (player.isShiftKeyDown()) { + if (!level().isClientSide()) { + if (this.canPlayerModify(player)) { + player.openMenu(new SimpleMenuProvider((syncId, playerInv, user) -> + new CannonContainerMenu(syncId, playerInv, this.inventory), UI_TITLE)); + } else { + level().playSound(null, this.blockPosition(), SoundEvents.CHEST_LOCKED, SoundSource.BLOCKS, 0.5f, 1.5f); + } + return InteractionResult.PASS; + } + return InteractionResult.SUCCESS; + } + + if (!this.isVehicle() && !this.getBehavior().occupiesCannon(this.inventory.getItem(2))) { + if (!level().isClientSide()) { + player.setYRot(this.getYRot()); + player.setXRot(this.getXRot()); + player.startRiding(this); + + return InteractionResult.PASS; + } + return InteractionResult.SUCCESS; + } else { + player.displayClientMessage(FULL_CANNON_DIALOG, true); + } + + return super.interact(player, hand); + } + + @Override + public boolean skipAttackInteraction(@NotNull Entity attacker) { + if (attacker instanceof Player player && player != this.getFirstPassenger()) { + ItemStack is = player.getItemInHand(InteractionHand.MAIN_HAND); + if (player.mayBuild() && + (player.isCreative() || is.getItem() instanceof PickaxeItem)) { + if (!this.level().isClientSide()) { + Containers.dropContents(this.level(), this.blockPosition(), this.inventory); + if (!player.isCreative()) { + Containers.dropItemStack(this.level(), this.getX(), this.getY(), this.getZ(), new ItemStack(BTRItems.CANNON_ITEM.get())); + } + this.remove(RemovalReason.KILLED); + } + this.level().playSound(null, this.blockPosition(), SoundEvents.STONE_BREAK, SoundSource.BLOCKS, + 1, 0.8f); + this.level().addDestroyBlockEffect(this.blockPosition(), Blocks.ANVIL.defaultBlockState()); + + return true; + } + } + + this.level().playSound(null, this.blockPosition(), SoundEvents.STONE_HIT, SoundSource.BLOCKS, + 1, 0.5f); + return true; + } + + @Override + public void lerpTo(double x, double y, double z, float yaw, float pitch, int interpolationSteps, boolean interpolate) { + if (!this.level().isClientSide()) { + this.setPos(x, y, z); + + if (!this.isVehicle()) { + this.setRot(yaw, pitch); + } + } else { + this.targetX = x; + this.targetY = y; + this.targetZ = z; + this.targetTicks = this.getType().updateInterval(); + } + } + + private void positionTrackTick() { + if (this.targetTicks > 0) { + this.setPos( + this.getX() + (this.targetX - this.getX()) / (double)this.targetTicks, + this.getY() + (this.targetY - this.getY()) / (double)this.targetTicks, + this.getZ() + (this.targetZ - this.getZ()) / (double)this.targetTicks + ); + + this.targetTicks--; + } + } + + private void movementTick() { + var vel = this.getDeltaMovement(); + + this.setDeltaMovement(new Vec3(vel.x * 0.9, this.onGround() ? 0 : Math.max(vel.y - 0.07, -0.7), vel.z * 0.9)); + this.hurtMarked = true; + } + + public ItemStack getBehaviorStack() { + if (!this.level().isClientSide()) { + return this.inventory.getItem(2); + } + + return this.entityData.get(BEHAVIOR_STACK); + } + + public void handleInput(boolean firing) { + if (this.level().isClientSide()) { + if (firing && !this.firing) { + BTRNetwork.CHANNEL.sendToServer(new RequestFirePayload(this)); + } + this.firing = firing; + } + } + + public void fireServer() { + if (this.level() instanceof ServerLevel world) { + var gunpowder = this.inventory.getItem(0); + if (gunpowder.is(Items.GUNPOWDER) && gunpowder.getCount() > 0) { + Player firedPlayer = null; + var behaviorStack = this.getBehaviorStack(); + var vel = getDeltaMovement().add(getLookAngle().scale(Math.sqrt(gunpowder.getCount()) * 0.6)); + this.getBehavior().onFired(this, behaviorStack, vel); + if (this.getFirstPassenger() instanceof Player player) { + player.stopRiding(); + player.setDeltaMovement(vel); + ((ServerPlayer)player).connection.send(new ClientboundSetEntityMotionPacket(player)); + ((ServerPlayer)player).connection.send(new ClientboundTeleportEntityPacket(player)); + player.hurtMarked = true; + ((PlayerEntityDuck)player).blasttravel$setCannonFlight(true); + firedPlayer = player; + } + + this.level().playSound(null, this.blockPosition(), SoundEvents.GENERIC_EXPLODE, SoundSource.BLOCKS, 1, 1); + for (var to : world.players()) { + BTRNetwork.CHANNEL.sendTo(new FireCannonPayload(this, firedPlayer, vel), to.connection.connection, NetworkDirection.PLAY_TO_CLIENT); + } + + this.updateStateFromInventory(); + } else { + if (this.getFirstPassenger() instanceof Player player) { + player.stopRiding(); + player.displayClientMessage(NO_GUNPOWDER_DIALOG, true); + } + + this.level().playSound(null, this.blockPosition(), SoundEvents.GENERIC_EXTINGUISH_FIRE, SoundSource.BLOCKS, 1, 0.8f); + } + } + } + + public void fireClient() { + if (!level().isClientSide()) { + return; + } + + this.animate(); + + final int ringParticles = 18; + for (int i = 0; i < ringParticles; i++) { + double angle = (2d / ringParticles) * Math.PI * i; + var arc = new Vec3(Math.sin(angle), Math.cos(angle), 0) + .xRot(-this.getXRot() * Mth.DEG_TO_RAD) + .yRot(-this.getYRot() * Mth.DEG_TO_RAD); + + var pos = this.position() + .add(0, 0.75, 0) + .add(this.calculateViewVector(this.getXRot() - 4, this.getYRot()) + .scale(1.69f)).add(arc.scale(0.15f)); + var vel = arc.scale(0.14); + + Minecraft.getInstance().particleEngine.createParticle(BTRParticleTypes.CANNON_BLAST.get(), + pos.x, pos.y, pos.z, vel.x, vel.y, vel.z); + } + } + + public void animate() { + this.animation = MAX_ANIMATION; + } + + protected int getBehaviorId() { + return this.entityData.get(BEHAVIOR); + } + + protected void setBehaviorId(int id) { + this.entityData.set(BEHAVIOR, id); + } + + public boolean hasFuse() { + return this.isVehicle() || this.getBehavior().occupiesCannon(this.inventory.getItem(2)); + } + + public boolean hasChains() { + return this.chained; + } + + public float getAnimation(float tickDelta) { + float anim = Math.max(0, this.animation - tickDelta); + return anim / MAX_ANIMATION; + } + + public int getAnimationTick() { + return this.animation; + } + + private void setChained(boolean chained) { + if (chained != this.chained) { + this.level().playSound(null, this.blockPosition(), SoundEvents.ARMOR_EQUIP_CHAIN, + SoundSource.BLOCKS, 1, 1.2f); + } + + this.chained = chained; + } + + @OnlyIn(Dist.CLIENT) + public @Nullable AbstractClientPlayer getClientPlayer() { + return this.getFirstPassenger() instanceof AbstractClientPlayer player ? player : null; + } + + public CannonBehavior getBehavior() { + return CannonBehavior.byId(getBehaviorId()); + } + + protected void updateStateFromInventory() { + if (!this.level().isClientSide()) { + for (int slot = 0; slot < this.inventory.getContainerSize(); slot++) { + var stack = this.inventory.getItem(slot); + if (slot == 1) { + setChained(stack.is(Items.CHAIN)); + } else if (slot == 2) { + this.setBehaviorId(CannonBehavior.idForStack(stack)); + this.entityData.set(BEHAVIOR_STACK, stack); + } + } + } + } + + @Nullable + @Override + public ItemStack getPickResult() { + return new ItemStack(BTRItems.CANNON_ITEM.get()); + } + + @Override + public boolean isPickable() { + return !this.isRemoved(); + } + + @Override + public boolean canCollideWith(Entity other) { + return (other.canBeCollidedWith() || other.isPushable()) && !this.isPassengerOfSameVehicle(other); + } + + @Override + public boolean canBeCollidedWith() { + return true; + } + + @Override + public double getPassengersRidingOffset() { + if (this.isVehicle()) { + var passenger = this.getFirstPassenger(); + if (passenger != null) { + return (-passenger.getMyRidingOffset() - passenger.getEyeHeight(passenger.getPose())) + 0.75f; + } + } + return super.getPassengersRidingOffset(); + } + + @Override + protected void defineSynchedData() { + this.entityData.define(BEHAVIOR, 0); + this.entityData.define(CHAINED, false); + this.entityData.define(BEHAVIOR_STACK, ItemStack.EMPTY); + } + + @Override + protected void readAdditionalSaveData(CompoundTag nbt) { + ContainerHelper.loadAllItems(nbt.getCompound("Items"), this.inventory.items); + this.powered = nbt.getBoolean("powered"); + this.alwaysModifiable = nbt.getBoolean("alwaysModifiable"); + + this.updateStateFromInventory(); + } + + @Override + protected void addAdditionalSaveData(CompoundTag nbt) { + var inv = new CompoundTag(); + ContainerHelper.saveAllItems(inv, this.inventory.items); + nbt.put("Items", inv); + nbt.putBoolean("powered", this.powered); + nbt.putBoolean("alwaysModifiable", this.alwaysModifiable); + } + +} diff --git a/src/main/java/com/leisuretimedock/blasttravelreborn/content/entity/cannon/CannonBehavior.java b/src/main/java/com/leisuretimedock/blasttravelreborn/content/entity/cannon/CannonBehavior.java new file mode 100644 index 0000000..4cd0a18 --- /dev/null +++ b/src/main/java/com/leisuretimedock/blasttravelreborn/content/entity/cannon/CannonBehavior.java @@ -0,0 +1,108 @@ +package com.leisuretimedock.blasttravelreborn.content.entity.cannon; + +import com.leisuretimedock.blasttravelreborn.BlastTravelReborn; +import com.leisuretimedock.blasttravelreborn.content.entity.CannonEntity; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import org.jetbrains.annotations.Nullable; +import org.joml.Vector3f; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.function.Predicate; + +public class CannonBehavior { + protected static final Vector3f WHITE = new Vector3f(1, 1, 1); + private static final List ID_TO_BEHAVIOR = new ArrayList<>(); + private static final Object2IntMap> FILTER_TO_BEHAVIOR_ID = new Object2IntOpenHashMap<>(); + + public final Item icon; + public final Predicate filter; + private final ResourceLocation texture; + + public CannonBehavior(Item item, ResourceLocation texture) { + this(item, stack -> stack.getItem() == item, texture); + } + + public CannonBehavior(Item icon, Predicate filter) { + this(icon, filter, BlastTravelReborn.id("textures/entity/cannon/regular.png")); + } + + public CannonBehavior(Item icon, Predicate filter, ResourceLocation texture) { + this.icon = icon; + this.filter = filter; + this.texture = texture; + } + + public CannonBehavior register() { + FILTER_TO_BEHAVIOR_ID.put(this.filter, ID_TO_BEHAVIOR.size()); + ID_TO_BEHAVIOR.add(this); + + return this; + } + + @OnlyIn(Dist.CLIENT) + public boolean displayHead(CannonEntity entity) { + return entity.getClientPlayer() != null; + } + + public boolean occupiesCannon(ItemStack behaviorStack) { + return false; + } + + public void onFired(CannonEntity cannon, ItemStack behaviorStack, Vec3 velocity) { + } + + public static CannonBehavior byId(int id) { + return ID_TO_BEHAVIOR.get(id); + } + + public static int idForStack(ItemStack stack) { + for (var e : FILTER_TO_BEHAVIOR_ID.object2IntEntrySet()) { + if (e.getKey().test(stack)) { + return e.getIntValue(); + } + } + return 0; + } + + public static boolean isValidBehaviorStack(ItemStack stack) { + return FILTER_TO_BEHAVIOR_ID.object2IntEntrySet().stream().anyMatch(e -> e.getKey().test(stack)); + } + + public static Collection allBehaviors() { + return ID_TO_BEHAVIOR; + } + + @OnlyIn(Dist.CLIENT) + public ResourceLocation texture(ItemStack stack) { + return texture; + } + + @OnlyIn(Dist.CLIENT) + public ResourceLocation headTexture(CannonEntity entity) { + var player = entity.getClientPlayer(); + if (player == null) { + return ResourceLocation.withDefaultNamespace("missing"); + } + + return player.getSkinTextureLocation(); + } + + @OnlyIn(Dist.CLIENT) + public Vector3f headColor(CannonEntity entity) { + return WHITE; + } + + @OnlyIn(Dist.CLIENT) + public @Nullable Vector3f fireColor(CannonEntity entity) { + return null; + } +} diff --git a/src/main/java/com/leisuretimedock/blasttravelreborn/content/entity/cannon/ConcretePowderCannonBehavior.java b/src/main/java/com/leisuretimedock/blasttravelreborn/content/entity/cannon/ConcretePowderCannonBehavior.java new file mode 100644 index 0000000..10bd233 --- /dev/null +++ b/src/main/java/com/leisuretimedock/blasttravelreborn/content/entity/cannon/ConcretePowderCannonBehavior.java @@ -0,0 +1,81 @@ +package com.leisuretimedock.blasttravelreborn.content.entity.cannon; + +import com.leisuretimedock.blasttravelreborn.BlastTravelReborn; +import com.leisuretimedock.blasttravelreborn.content.entity.CannonEntity; +import net.minecraft.core.particles.ItemParticleOption; +import net.minecraft.core.particles.ParticleTypes; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.block.ConcretePowderBlock; +import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.Nullable; +import org.joml.Vector3f; + +import java.util.HashMap; +import java.util.Map; + +public class ConcretePowderCannonBehavior extends CannonBehavior { + private static final ResourceLocation TEXTURE = BlastTravelReborn.id("textures/entity/cannon/head/powder.png"); + private static final Map COLORS = new HashMap<>(); + + public ConcretePowderCannonBehavior() { + super(Items.WHITE_CONCRETE_POWDER, + s -> s.getItem() instanceof BlockItem item && item.getBlock() instanceof ConcretePowderBlock); + } + + @Override + public boolean displayHead(CannonEntity entity) { + return true; + } + + private Vector3f color(ItemStack stack) { + if (stack.getItem() instanceof BlockItem item && item.getBlock() instanceof ConcretePowderBlock block) { + int color = block.defaultMapColor().col; + + return COLORS.computeIfAbsent(item, i -> + new Vector3f((float)((color >> 16) & 0xFF) / 255, + (float)((color >> 8) & 0xFF) / 255, + (float)(color & 0xFF) / 255)); + } + + return WHITE; + } + + @Override + public void onFired(CannonEntity cannon, ItemStack behaviorStack, Vec3 velocity) { + if (cannon.level() instanceof ServerLevel world) { + var rot = cannon.getLookAngle(); + var origin = cannon.position().add(0, 0.75, 0).add(rot.scale(1.8)); + + world.sendParticles(new ItemParticleOption(ParticleTypes.ITEM, behaviorStack), origin.x, origin.y, origin.z, + 12 + world.random.nextInt(7), 0, 0, 0, 0.17); + } + } + + @Override + public ResourceLocation headTexture(CannonEntity entity) { + if (!entity.isVehicle()) { + return TEXTURE; + } + + return super.headTexture(entity); + } + + @Override + public Vector3f headColor(CannonEntity entity) { + if (!entity.isVehicle()) { + return this.color(entity.getBehaviorStack()); + } + + return super.headColor(entity); + } + + @Override + public @Nullable Vector3f fireColor(CannonEntity entity) { + return this.color(entity.getBehaviorStack()); + } +} diff --git a/src/main/java/com/leisuretimedock/blasttravelreborn/content/entity/cannon/EntityCannonBehavior.java b/src/main/java/com/leisuretimedock/blasttravelreborn/content/entity/cannon/EntityCannonBehavior.java new file mode 100644 index 0000000..8ef0f49 --- /dev/null +++ b/src/main/java/com/leisuretimedock/blasttravelreborn/content/entity/cannon/EntityCannonBehavior.java @@ -0,0 +1,57 @@ +package com.leisuretimedock.blasttravelreborn.content.entity.cannon; + +import com.leisuretimedock.blasttravelreborn.content.entity.CannonEntity; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.item.FallingBlockEntity; +import net.minecraft.world.entity.item.PrimedTnt; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.phys.Vec3; + +import java.util.function.Predicate; + +public class EntityCannonBehavior extends CannonBehavior { + private final EntityFactory entityFactory; + + public EntityCannonBehavior(Item item, ResourceLocation texture, EntityFactory entityFactory) { + this(item, stack -> stack.is(item), texture, entityFactory); + } + + public EntityCannonBehavior(Item icon, Predicate filter, ResourceLocation texture, EntityFactory entityFactory) { + super(icon, filter, texture); + this.entityFactory = entityFactory; + } + + @Override + public boolean occupiesCannon(ItemStack behaviorStack) { + return true; + } + + @Override + public void onFired(CannonEntity cannon, ItemStack behaviorStack, Vec3 velocity) { + var pos = cannon.position().add(0, 0.75, 0).add(cannon.getLookAngle().scale(1.8)); + var entity = entityFactory.create(cannon.level(), pos, behaviorStack); + entity.setDeltaMovement(velocity); + behaviorStack.shrink(1); + + cannon.level().addFreshEntity(entity); + } + + public static Entity tntFactory(Level world, Vec3 pos, ItemStack from) { + return new PrimedTnt(world, pos.x, pos.y - 0.5, pos.z, null); + } + + public static Entity fallingBlockFactory(Level world, Vec3 pos, ItemStack from) { + return new FallingBlockEntity(world, pos.x, pos.y - 0.5, pos.z, + from.getItem() instanceof BlockItem block ? block.getBlock().defaultBlockState() : Blocks.AIR.defaultBlockState()); + } + + @FunctionalInterface + public interface EntityFactory { + Entity create(Level world, Vec3 pos, ItemStack spawnedFrom); + } +} diff --git a/src/main/java/com/leisuretimedock/blasttravelreborn/content/item/BTRItems.java b/src/main/java/com/leisuretimedock/blasttravelreborn/content/item/BTRItems.java new file mode 100644 index 0000000..38586eb --- /dev/null +++ b/src/main/java/com/leisuretimedock/blasttravelreborn/content/item/BTRItems.java @@ -0,0 +1,20 @@ +package com.leisuretimedock.blasttravelreborn.content.item; + +import com.leisuretimedock.blasttravelreborn.BlastTravelReborn; +import net.minecraft.world.item.Item; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegistryObject; + +public class BTRItems { + public static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, BlastTravelReborn.MOD_ID); + public static final RegistryObject CANNON_ITEM = + ITEMS.register( + "cannon", + () -> new CannonItem(new Item.Properties()) + ); + public static void register(IEventBus eventBus) { + ITEMS.register(eventBus); + } +} diff --git a/src/main/java/com/leisuretimedock/blasttravelreborn/content/item/CannonItem.java b/src/main/java/com/leisuretimedock/blasttravelreborn/content/item/CannonItem.java new file mode 100644 index 0000000..251a749 --- /dev/null +++ b/src/main/java/com/leisuretimedock/blasttravelreborn/content/item/CannonItem.java @@ -0,0 +1,30 @@ +package com.leisuretimedock.blasttravelreborn.content.item; + +import com.leisuretimedock.blasttravelreborn.content.entity.BTREntityTypes; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.MobSpawnType; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.context.UseOnContext; +import org.jetbrains.annotations.NotNull; + +public class CannonItem extends Item { + public CannonItem(Properties settings) { + super(settings); + } + + @Override + public @NotNull InteractionResult useOn(UseOnContext context) { + if (context.getLevel() instanceof ServerLevel world) { + BTREntityTypes.CANNON_ENTITY.get().spawn(world, context.getItemInHand(), context.getPlayer(), context.getClickedPos().relative(context.getClickedFace()), MobSpawnType.SPAWN_EGG, true, false); + if (context.getPlayer() != null && !context.getPlayer().isCreative()) { + context.getItemInHand().shrink(1); + } + } + context.getLevel().playSound(null, context.getClickedPos(), SoundEvents.STONE_PLACE, SoundSource.BLOCKS, 1, 0.8f); + + return InteractionResult.sidedSuccess(context.getLevel().isClientSide()); + } +} diff --git a/src/main/java/com/leisuretimedock/blasttravelreborn/content/menu/BTRMenuTypes.java b/src/main/java/com/leisuretimedock/blasttravelreborn/content/menu/BTRMenuTypes.java new file mode 100644 index 0000000..0d9956f --- /dev/null +++ b/src/main/java/com/leisuretimedock/blasttravelreborn/content/menu/BTRMenuTypes.java @@ -0,0 +1,21 @@ +package com.leisuretimedock.blasttravelreborn.content.menu; + +import com.leisuretimedock.blasttravelreborn.BlastTravelReborn; +import net.minecraft.world.flag.FeatureFlags; +import net.minecraft.world.inventory.MenuType; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegistryObject; + +public class BTRMenuTypes { + public static final DeferredRegister> MENU_TYPES = DeferredRegister.create(ForgeRegistries.MENU_TYPES, BlastTravelReborn.MOD_ID); + public static final RegistryObject> CANNON_CONTAINER_MENU = + MENU_TYPES.register( + "cannon_container_menu", + () -> new MenuType<>(CannonContainerMenu::new, FeatureFlags.VANILLA_SET) + ); + public static void register(IEventBus eventBus) { + MENU_TYPES.register(eventBus); + } +} diff --git a/src/main/java/com/leisuretimedock/blasttravelreborn/content/menu/CannonContainerMenu.java b/src/main/java/com/leisuretimedock/blasttravelreborn/content/menu/CannonContainerMenu.java new file mode 100644 index 0000000..4d4218e --- /dev/null +++ b/src/main/java/com/leisuretimedock/blasttravelreborn/content/menu/CannonContainerMenu.java @@ -0,0 +1,90 @@ +package com.leisuretimedock.blasttravelreborn.content.menu; + +import com.leisuretimedock.blasttravelreborn.content.entity.cannon.CannonBehavior; +import net.minecraft.world.Container; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import org.jetbrains.annotations.NotNull; + +import java.util.function.Predicate; + +public class CannonContainerMenu extends AbstractContainerMenu { + public final Container inventory; + + public CannonContainerMenu(int syncId, Inventory playerInv) { + this(syncId, playerInv, new SimpleContainer(3)); + } + + public CannonContainerMenu(int syncId, Inventory playerInv, Container inv) { + super(BTRMenuTypes.CANNON_CONTAINER_MENU.get(), syncId); + + checkContainerSize(inv, 3); + this.inventory = inv; + inv.startOpen(playerInv.player); + + this.addSlot(new FilterSlot(inv, 0, 62, 20, stack -> stack.is(Items.GUNPOWDER))); + this.addSlot(new FilterSlot(inv, 1, 80, 20, stack -> stack.is(Items.CHAIN))); + this.addSlot(new FilterSlot(inv, 2, 98, 20, CannonBehavior::isValidBehaviorStack)); + + int row; + int col; + for(row = 0; row < 3; ++row) { + for(col = 0; col < 9; ++col) { + this.addSlot(new Slot(playerInv, col + row * 9 + 9, 8 + col * 18, 58 + row * 18)); + } + } + for(row = 0; row < 9; ++row) { + this.addSlot(new Slot(playerInv, row, 8 + row * 18, 116)); + } + } + + @Override + public @NotNull ItemStack quickMoveStack(@NotNull Player player, int fromSlotId) { + var newStack = ItemStack.EMPTY; + var fromSlot = this.slots.get(fromSlotId); + + if (fromSlot.hasItem()) { + var fromStack = fromSlot.getItem(); + newStack = fromStack.copy(); + if (fromSlotId >= 0 && fromSlotId < 3) { + if (!this.moveItemStackTo(fromStack, 3, 39, true)) { + return ItemStack.EMPTY; + } + } else if (!this.moveItemStackTo(fromStack, 0, 3, false)) { + return ItemStack.EMPTY; + } + + if (fromStack.isEmpty()) { + fromSlot.set(ItemStack.EMPTY); + } else { + fromSlot.setChanged(); + } + } + + return newStack; + } + + @Override + public boolean stillValid(@NotNull Player player) { + return true; + } + + public static class FilterSlot extends Slot { + private final Predicate filter; + + public FilterSlot(Container inventory, int id, int x, int y, Predicate filter) { + super(inventory, id, x, y); + this.filter = filter; + } + + @Override + public boolean mayPlace(@NotNull ItemStack stack) { + return super.mayPlace(stack) && filter.test(stack); + } + } +} diff --git a/src/main/java/com/leisuretimedock/blasttravelreborn/mixin/ClientPlayNetworkHandlerMixin.java b/src/main/java/com/leisuretimedock/blasttravelreborn/mixin/ClientPlayNetworkHandlerMixin.java new file mode 100644 index 0000000..5af11f7 --- /dev/null +++ b/src/main/java/com/leisuretimedock/blasttravelreborn/mixin/ClientPlayNetworkHandlerMixin.java @@ -0,0 +1,39 @@ +package com.leisuretimedock.blasttravelreborn.mixin; + +import com.leisuretimedock.blasttravelreborn.content.entity.CannonEntity; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientPacketListener; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.game.ClientboundSetPassengersPacket; +import net.minecraft.world.entity.Entity; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +@Mixin(ClientPacketListener.class) +public class ClientPlayNetworkHandlerMixin { + @Shadow @Final private Minecraft minecraft; + @Unique private Entity blasttravel$cachedMount = null; + + @Inject(method = "handleSetEntityPassengersPacket", locals = LocalCapture.CAPTURE_FAILEXCEPTION, + at = @At(value = "INVOKE_ASSIGN", ordinal = 0, shift = At.Shift.AFTER, target = "Lnet/minecraft/client/multiplayer/ClientLevel;getEntity(I)Lnet/minecraft/world/entity/Entity;")) + private void blasttravel$cacheMountedEntity(ClientboundSetPassengersPacket packet, CallbackInfo ci, Entity mounted) { + blasttravel$cachedMount = mounted; + } + + @ModifyVariable(method = "handleSetEntityPassengersPacket", index = 9, + at = @At(value = "INVOKE_ASSIGN", ordinal = 0, shift = At.Shift.AFTER, target = "Lnet/minecraft/network/chat/Component;translatable(Ljava/lang/String;[Ljava/lang/Object;)Lnet/minecraft/network/chat/MutableComponent;")) + private Component blasttravel$modifyMountMessage(Component old) { + if (blasttravel$cachedMount instanceof CannonEntity) { + return Component.translatable("mount.blasttravelreborn.cannon.onboard", + this.minecraft.options.keyShift.getTranslatedKeyMessage(), this.minecraft.options.keyJump.getTranslatedKeyMessage()); + } + return old; + } +} diff --git a/src/main/java/com/leisuretimedock/blasttravelreborn/mixin/ClientPlayerEntityMixin.java b/src/main/java/com/leisuretimedock/blasttravelreborn/mixin/ClientPlayerEntityMixin.java new file mode 100644 index 0000000..b4fc7d5 --- /dev/null +++ b/src/main/java/com/leisuretimedock/blasttravelreborn/mixin/ClientPlayerEntityMixin.java @@ -0,0 +1,39 @@ +package com.leisuretimedock.blasttravelreborn.mixin; + +import com.leisuretimedock.blasttravelreborn.content.entity.CannonEntity; +import com.leisuretimedock.blasttravelreborn.util.PlayerEntityDuck; +import com.mojang.authlib.GameProfile; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.client.player.Input; +import net.minecraft.client.player.LocalPlayer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(LocalPlayer.class) +public abstract class ClientPlayerEntityMixin extends AbstractClientPlayer implements PlayerEntityDuck { + @Shadow public Input input; + + public ClientPlayerEntityMixin(ClientLevel world, GameProfile profile) { + super(world, profile); + } + + @Inject(method = "rideTick", at = @At("TAIL")) + private void blasttravel$handleCannonInput(CallbackInfo ci) { + if (this.getVehicle() instanceof CannonEntity cannon) { + cannon.handleInput(this.input.jumping); + } + } + + @Inject(method = "serverAiStep", at = @At("TAIL")) + private void blasttravel$preserveHSpeedInFlight(CallbackInfo ci) { + if (this.blasttravel$inCannonFlight()) { + var self = this; + self.xxa = 0; + self.zza = 0; + } + } +} diff --git a/src/main/java/com/leisuretimedock/blasttravelreborn/mixin/LivingEntityAccess.java b/src/main/java/com/leisuretimedock/blasttravelreborn/mixin/LivingEntityAccess.java new file mode 100644 index 0000000..b03373d --- /dev/null +++ b/src/main/java/com/leisuretimedock/blasttravelreborn/mixin/LivingEntityAccess.java @@ -0,0 +1,11 @@ +package com.leisuretimedock.blasttravelreborn.mixin; + +import net.minecraft.world.entity.LivingEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(LivingEntity.class) +public interface LivingEntityAccess { + @Accessor("discardFriction") + void blasttravel$setNoDrag(boolean noDrag); +} diff --git a/src/main/java/com/leisuretimedock/blasttravelreborn/mixin/PlayerEntityMixin.java b/src/main/java/com/leisuretimedock/blasttravelreborn/mixin/PlayerEntityMixin.java new file mode 100644 index 0000000..1980c0d --- /dev/null +++ b/src/main/java/com/leisuretimedock/blasttravelreborn/mixin/PlayerEntityMixin.java @@ -0,0 +1,151 @@ +package com.leisuretimedock.blasttravelreborn.mixin; + +import com.leisuretimedock.blasttravelreborn.BlastTravelReborn; +import com.leisuretimedock.blasttravelreborn.network.BTRNetwork; +import com.leisuretimedock.blasttravelreborn.network.toServer.StopCannonFlightServerPayload; +import com.leisuretimedock.blasttravelreborn.util.PlayerEntityDuck; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.util.Mth; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.EntityDimensions; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.Pose; +import net.minecraft.world.entity.player.Abilities; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.entity.EntityTypeTest; +import net.minecraft.world.phys.Vec3; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(Player.class) +public abstract class PlayerEntityMixin extends LivingEntity implements PlayerEntityDuck { + @Shadow + public abstract boolean isLocalPlayer(); + + @Shadow + public abstract Abilities getAbilities(); + + @Unique + private static final EntityDimensions CANNON_FLIGHT_DIMENSIONS = EntityDimensions.scalable(0.6f, 0.6f); + @Unique + private Vec3 blasttravel$vel = Vec3.ZERO; + @Unique + private Vec3 blasttravel$trackingVel = Vec3.ZERO; + @Unique + private Vec3 blasttravel$prevVel = Vec3.ZERO; + + @Unique + private boolean blasttravel$inCannonFlight = false; + @Unique + private boolean blasttravel$cancelFallDamage = false; + + @Unique + private int blasttravel$ticksFlying = 0; + + protected PlayerEntityMixin(EntityType entityType, Level world) { + super(entityType, world); + } + + @Inject(method = "tick", at = @At("HEAD")) + private void blasttravel$beginTick(CallbackInfo ci) { + this.blasttravel$prevVel = this.isLocalPlayer() ? blasttravel$vel : blasttravel$trackingVel; + } + + @Inject(method = "tick", at = @At("TAIL")) + private void blasttravel$endTick(CallbackInfo ci) { + var self = this; + this.blasttravel$vel = self.position().subtract(self.xo, self.yo, self.zo); + + if (this.blasttravel$inCannonFlight()) { + if (!self.level().isClientSide()) { + var vel = self.getDeltaMovement(); + var frontBox = self.getBoundingBox().expandTowards(0.2, 0.2, 0.2); + for (var entity : self.level().getEntities(EntityTypeTest.forClass(LivingEntity.class), frontBox, entity -> entity != self)) { + if (!entity.isInvulnerable()) { + DamageSource source = self.damageSources().source(ResourceKey.create(Registries.DAMAGE_TYPE, BlastTravelReborn.id("cannon")), self); + entity.hurt(source, (float)(vel.length() * 4)); + } + } + } + + if (self.isLocalPlayer() && + this.blasttravel$ticksFlying > 4 && + (self.onGround() || self.isFallFlying() || self.getAbilities().flying || self.isUnderWater())) { + this.blasttravel$setCannonFlight(false); + BTRNetwork.CHANNEL.sendToServer(new StopCannonFlightServerPayload(self.onGround())); + } + + this.blasttravel$ticksFlying++; + } else { + this.blasttravel$ticksFlying = 0; + } + + if (!self.isLocalPlayer()) { + this.blasttravel$trackingVel = this.blasttravel$trackingVel.add( + this.blasttravel$vel.subtract(this.blasttravel$trackingVel).scale(1f / self.getType().updateInterval())); + } + } + + @Inject(method = "causeFallDamage", at = @At("HEAD"), cancellable = true) + private void blasttravel$cancelFallDamage(float fallDistance, float damageMultiplier, DamageSource damageSource, CallbackInfoReturnable cir) { + if (this.blasttravel$cancelFallDamage) { + this.blasttravel$cancelFallDamage = false; + cir.setReturnValue(false); + } + } + + @Inject(method = "getDimensions", at = @At("HEAD"), cancellable = true) + private void blasttravel$setFlyingPose(Pose pose, CallbackInfoReturnable cir) { + if (this.blasttravel$inCannonFlight()) { + cir.setReturnValue(CANNON_FLIGHT_DIMENSIONS); + } + } + + @Inject(method = "getStandingEyeHeight", at = @At("HEAD"), cancellable = true) + private void blasttravel$setFlyingEyeHeight(Pose pose, EntityDimensions dimensions, CallbackInfoReturnable cir) { + if (this.blasttravel$inCannonFlight()) { + cir.setReturnValue(dimensions.height * 0.5f); + } + } + + @Override + public void blasttravel$setCannonFlight(boolean inFlight) { + var self = this; + + if (inFlight && !this.blasttravel$inCannonFlight) { + this.blasttravel$vel = + this.blasttravel$trackingVel = + this.blasttravel$prevVel = self.getDeltaMovement(); + this.blasttravel$cancelFallDamage = true; + + self.setJumping(false); + } + + this.blasttravel$inCannonFlight = inFlight; + ((LivingEntityAccess)this).blasttravel$setNoDrag(inFlight); + + self.refreshDimensions(); + } + + @Override + public boolean blasttravel$inCannonFlight() { + return this.blasttravel$inCannonFlight; + } + + @Override + public Vec3 blasttravel$getVelocityLerped(float delta) { + var vel = this.isLocalPlayer() ? blasttravel$vel : blasttravel$trackingVel; + return new Vec3( + Mth.lerp(delta, blasttravel$prevVel.x, vel.x), + Mth.lerp(delta, blasttravel$prevVel.y, vel.y), + Mth.lerp(delta, blasttravel$prevVel.z, vel.z)); + } +} diff --git a/src/main/java/com/leisuretimedock/blasttravelreborn/mixin/PlayerEntityMixinClient.java b/src/main/java/com/leisuretimedock/blasttravelreborn/mixin/PlayerEntityMixinClient.java new file mode 100644 index 0000000..8b879d9 --- /dev/null +++ b/src/main/java/com/leisuretimedock/blasttravelreborn/mixin/PlayerEntityMixinClient.java @@ -0,0 +1,29 @@ +package com.leisuretimedock.blasttravelreborn.mixin; + +import com.leisuretimedock.blasttravelreborn.util.PlayerEntityDuck; +import net.minecraft.client.Minecraft; +import net.minecraft.core.particles.ParticleTypes; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Player.class) +public abstract class PlayerEntityMixinClient extends LivingEntity implements PlayerEntityDuck { + protected PlayerEntityMixinClient(EntityType entityType, Level world) { + super(entityType, world); + } + + @Inject(method = "tick", at = @At("TAIL")) + private void blasttravel$endTick(CallbackInfo ci) { + var self = this; + if (this.blasttravel$inCannonFlight() && self.level().isClientSide()) { + Minecraft.getInstance().particleEngine.createParticle(ParticleTypes.CAMPFIRE_COSY_SMOKE, + self.xo, self.yo, self.zo, 0, 0, 0); + } + } +} diff --git a/src/main/java/com/leisuretimedock/blasttravelreborn/mixin/PlayerEntityModelMixin.java b/src/main/java/com/leisuretimedock/blasttravelreborn/mixin/PlayerEntityModelMixin.java new file mode 100644 index 0000000..07c59ab --- /dev/null +++ b/src/main/java/com/leisuretimedock/blasttravelreborn/mixin/PlayerEntityModelMixin.java @@ -0,0 +1,45 @@ +package com.leisuretimedock.blasttravelreborn.mixin; + +import com.leisuretimedock.blasttravelreborn.util.PlayerEntityDuck; +import net.minecraft.client.model.HumanoidModel; +import net.minecraft.client.model.PlayerModel; +import net.minecraft.client.model.geom.ModelPart; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.LivingEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(PlayerModel.class) +public abstract class PlayerEntityModelMixin extends HumanoidModel { + @Unique private LivingEntity blasttravel$cached; + + public PlayerEntityModelMixin(ModelPart modelPart) { + super(modelPart); + } + + @Inject(method = "setupAnim*", at = @At("HEAD")) + private void blasttravel$cacheEntity(T entity, float f, float g, float h, float i, float j, CallbackInfo ci) { + this.blasttravel$cached = entity; + } + + @Inject(method = "setupAnim*", at = @At("TAIL")) + private void blasttravel$setHeadAngle(T entity, float f, float g, float h, float i, float j, CallbackInfo ci) { + if (entity instanceof PlayerEntityDuck duck && duck.blasttravel$inCannonFlight()) { + this.head.setRotation(-0.5f * Mth.PI, 0, 0); + this.hat.setRotation(-0.5f * Mth.PI, 0, 0); + } + } + + @ModifyVariable(method = "setupAnim*", at = @At("HEAD"), index = 2, argsOnly = true) + private float blasttravel$overrideLimbAngles(float old) { + if (this.blasttravel$cached instanceof PlayerEntityDuck duck && duck.blasttravel$inCannonFlight()) { + this.blasttravel$cached = null; + return 0.75f * Mth.PI; + } + return old; + } +} diff --git a/src/main/java/com/leisuretimedock/blasttravelreborn/mixin/PlayerEntityRendererMixin.java b/src/main/java/com/leisuretimedock/blasttravelreborn/mixin/PlayerEntityRendererMixin.java new file mode 100644 index 0000000..21feb5e --- /dev/null +++ b/src/main/java/com/leisuretimedock/blasttravelreborn/mixin/PlayerEntityRendererMixin.java @@ -0,0 +1,43 @@ +package com.leisuretimedock.blasttravelreborn.mixin; + +import com.leisuretimedock.blasttravelreborn.content.entity.CannonEntity; +import com.leisuretimedock.blasttravelreborn.util.PlayerEntityDuck; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.math.Axis; +import net.minecraft.client.model.PlayerModel; +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.entity.EntityRendererProvider; +import net.minecraft.client.renderer.entity.LivingEntityRenderer; +import net.minecraft.client.renderer.entity.player.PlayerRenderer; +import net.minecraft.util.Mth; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(PlayerRenderer.class) +public abstract class PlayerEntityRendererMixin extends LivingEntityRenderer> { + public PlayerEntityRendererMixin(EntityRendererProvider.Context context, PlayerModel entityModel, float f) { + super(context, entityModel, f); + } + + @Inject(method = "render*", at = @At("HEAD"), cancellable = true) + private void blasttravel$makePlayersInCannonsInvisible(AbstractClientPlayer player, float f, float g, PoseStack matrixStack, MultiBufferSource vertexConsumerProvider, int i, CallbackInfo ci) { + if (player.getVehicle() instanceof CannonEntity) { + ci.cancel(); + } + } + + @Inject(method = "setupRotations(Lnet/minecraft/client/player/AbstractClientPlayer;Lcom/mojang/blaze3d/vertex/PoseStack;FFF)V", at = @At("HEAD"), cancellable = true) + private void blasttravel$modifyPlayerAngles(AbstractClientPlayer player, PoseStack matrices, float f, float g, float tickDelta, CallbackInfo ci) { + if (player instanceof PlayerEntityDuck duck && duck.blasttravel$inCannonFlight()) { + var vel = duck.blasttravel$getVelocityLerped(tickDelta); + double horizontal = Math.sqrt(vel.x * vel.x + vel.z * vel.z); + super.setupRotations(player, matrices, f, 270 + ((float) Math.atan2(vel.z, vel.x) * Mth.RAD_TO_DEG), tickDelta); + matrices.mulPose(Axis.XP.rotation((Mth.PI * 1.5f) + (float) Math.atan2(vel.y, horizontal))); + + ci.cancel(); + } + } +} diff --git a/src/main/java/com/leisuretimedock/blasttravelreborn/network/BTRNetwork.java b/src/main/java/com/leisuretimedock/blasttravelreborn/network/BTRNetwork.java new file mode 100644 index 0000000..19fd6d2 --- /dev/null +++ b/src/main/java/com/leisuretimedock/blasttravelreborn/network/BTRNetwork.java @@ -0,0 +1,50 @@ +package com.leisuretimedock.blasttravelreborn.network; + +import com.leisuretimedock.blasttravelreborn.BlastTravelReborn; +import com.leisuretimedock.blasttravelreborn.network.toClient.FireCannonPayload; +import com.leisuretimedock.blasttravelreborn.network.toClient.StopCannonFlightClientPayload; +import com.leisuretimedock.blasttravelreborn.network.toServer.RequestFirePayload; +import com.leisuretimedock.blasttravelreborn.network.toServer.StopCannonFlightServerPayload; +import net.minecraftforge.network.NetworkDirection; +import net.minecraftforge.network.NetworkRegistry; +import net.minecraftforge.network.simple.SimpleChannel; + +public class BTRNetwork { + private static final String PROTOCOL_VERSION = "1"; + private static int ID = -1; + public static final SimpleChannel CHANNEL = NetworkRegistry.newSimpleChannel( + BlastTravelReborn.id("network"), + () -> PROTOCOL_VERSION, + PROTOCOL_VERSION::equals, + PROTOCOL_VERSION::equals + ); + public static void register() { + CHANNEL + .messageBuilder(FireCannonPayload.class, getIDAndIncrease(), NetworkDirection.PLAY_TO_CLIENT) + .encoder(FireCannonPayload::write) + .decoder(FireCannonPayload::read) + .consumerNetworkThread(FireCannonPayload::handler) + .add(); + CHANNEL + .messageBuilder(StopCannonFlightServerPayload.class, getIDAndIncrease(), NetworkDirection.PLAY_TO_SERVER) + .encoder(StopCannonFlightServerPayload::write) + .decoder(StopCannonFlightServerPayload::read) + .consumerNetworkThread(StopCannonFlightServerPayload::handler) + .add(); + CHANNEL + .messageBuilder(StopCannonFlightClientPayload.class, getIDAndIncrease(), NetworkDirection.PLAY_TO_CLIENT) + .encoder(StopCannonFlightClientPayload::write) + .decoder(StopCannonFlightClientPayload::read) + .consumerNetworkThread(StopCannonFlightClientPayload::handle) + .add(); + CHANNEL + .messageBuilder(RequestFirePayload.class, getIDAndIncrease(), NetworkDirection.PLAY_TO_SERVER) + .encoder(RequestFirePayload::write) + .decoder(RequestFirePayload::read) + .consumerNetworkThread(RequestFirePayload::handle) + .add(); + } + private static int getIDAndIncrease() { + return ++ID; + } +} diff --git a/src/main/java/com/leisuretimedock/blasttravelreborn/network/toClient/FireCannonPayload.java b/src/main/java/com/leisuretimedock/blasttravelreborn/network/toClient/FireCannonPayload.java new file mode 100644 index 0000000..375fb19 --- /dev/null +++ b/src/main/java/com/leisuretimedock/blasttravelreborn/network/toClient/FireCannonPayload.java @@ -0,0 +1,62 @@ +package com.leisuretimedock.blasttravelreborn.network.toClient; + +import com.leisuretimedock.blasttravelreborn.content.entity.CannonEntity; +import com.leisuretimedock.blasttravelreborn.util.PlayerEntityDuck; +import net.minecraft.client.multiplayer.ClientPacketListener; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.network.NetworkEvent; +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; +import java.util.function.Supplier; + +public record FireCannonPayload(int cannonId, Optional launchedId, double velocityX, double velocityY, double velocityZ) { + public FireCannonPayload(FriendlyByteBuf buf) { + this( + buf.readInt(), buf.readOptional(FriendlyByteBuf::readInt), + buf.readDouble(), buf.readDouble(), buf.readDouble() + ); + } + public FireCannonPayload(CannonEntity cannon, @Nullable Player launched, Vec3 velocity) { + this( + cannon.getId(), Optional.ofNullable(launched).map(Entity::getId), + velocity.x, velocity.y, velocity.z + ); + } + public static FireCannonPayload read(FriendlyByteBuf buf) { + return new FireCannonPayload(buf); + } + public void write(FriendlyByteBuf buf) { + buf.writeInt(cannonId); + buf.writeOptional(launchedId, FriendlyByteBuf::writeInt); + buf.writeDouble(velocityX); + buf.writeDouble(velocityY); + buf.writeDouble(velocityZ); + } + public void handler(Supplier ctx) { + NetworkEvent.Context context = ctx.get(); + if(context.getNetworkManager().getPacketListener() instanceof ClientPacketListener clientPacketListener) { + context.enqueueWork(() -> { + if(hasPlayer()) { + //noinspection OptionalGetWithoutIsPresent + if (clientPacketListener.getLevel().getEntity(launchedId.get()) instanceof Player launchedPlayer) { + launchedPlayer.getAbilities().flying = false; + launchedPlayer.setDeltaMovement(velocityX, velocityY, velocityZ); + ((PlayerEntityDuck)launchedPlayer).blasttravel$setCannonFlight(true); + } + } + Entity entity = clientPacketListener.getLevel().getEntity(cannonId); + if (entity instanceof CannonEntity cannonEntity) { + cannonEntity.fireClient(); + } + }); + context.setPacketHandled(true); + } + } + private boolean hasPlayer() { + return launchedId.isPresent(); + } +} diff --git a/src/main/java/com/leisuretimedock/blasttravelreborn/network/toClient/StopCannonFlightClientPayload.java b/src/main/java/com/leisuretimedock/blasttravelreborn/network/toClient/StopCannonFlightClientPayload.java new file mode 100644 index 0000000..29cae20 --- /dev/null +++ b/src/main/java/com/leisuretimedock/blasttravelreborn/network/toClient/StopCannonFlightClientPayload.java @@ -0,0 +1,36 @@ +package com.leisuretimedock.blasttravelreborn.network.toClient; + +import com.leisuretimedock.blasttravelreborn.util.PlayerEntityDuck; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientPacketListener; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraftforge.network.NetworkEvent; + +import java.util.function.Supplier; + +public record StopCannonFlightClientPayload(int flyingId) { + public StopCannonFlightClientPayload(FriendlyByteBuf buf) { + this(buf.readInt()); + } + public StopCannonFlightClientPayload(Player flying) { + this(flying.getId()); + } + public void write(FriendlyByteBuf buf) { + buf.writeInt(flyingId); + } + public static StopCannonFlightClientPayload read (FriendlyByteBuf buf) { + return new StopCannonFlightClientPayload(buf.readInt()); + } + public void handle(Supplier ctx) { + NetworkEvent.Context context = ctx.get(); + if (context.getNetworkManager().getPacketListener() instanceof ClientPacketListener packetListener) { + Entity entity = packetListener.getLevel().getEntity(flyingId); + if (entity instanceof PlayerEntityDuck playerEntityDuck && entity != Minecraft.getInstance().player) { + playerEntityDuck.blasttravel$setCannonFlight(false); + } + context.setPacketHandled(true); + } + } +} diff --git a/src/main/java/com/leisuretimedock/blasttravelreborn/network/toServer/RequestFirePayload.java b/src/main/java/com/leisuretimedock/blasttravelreborn/network/toServer/RequestFirePayload.java new file mode 100644 index 0000000..f323c94 --- /dev/null +++ b/src/main/java/com/leisuretimedock/blasttravelreborn/network/toServer/RequestFirePayload.java @@ -0,0 +1,39 @@ +package com.leisuretimedock.blasttravelreborn.network.toServer; + +import com.leisuretimedock.blasttravelreborn.content.entity.CannonEntity; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.world.entity.Entity; +import net.minecraftforge.network.NetworkEvent; + +import java.util.function.Supplier; + +public record RequestFirePayload(int cannonId) { + public RequestFirePayload(CannonEntity cannonEntity) { + this(cannonEntity.getId()); + } + public RequestFirePayload(FriendlyByteBuf buf) { + this(buf.readInt()); + } + public void write(FriendlyByteBuf buf) { + buf.writeInt(cannonId); + } + public static RequestFirePayload read(FriendlyByteBuf buf) { + return new RequestFirePayload(buf.readInt()); + } + public void handle(Supplier ctx) { + NetworkEvent.Context context = ctx.get(); + if (context.getNetworkManager().getPacketListener() instanceof ServerGamePacketListenerImpl) { + context.enqueueWork(() -> { + ServerPlayer sender = context.getSender(); + assert sender != null; + Entity entity = sender.level().getEntity(cannonId); + if (entity instanceof CannonEntity cannonEntity) { + cannonEntity.fireServer(); + } + }); + context.setPacketHandled(true); + } + } +} diff --git a/src/main/java/com/leisuretimedock/blasttravelreborn/network/toServer/StopCannonFlightServerPayload.java b/src/main/java/com/leisuretimedock/blasttravelreborn/network/toServer/StopCannonFlightServerPayload.java new file mode 100644 index 0000000..f72f621 --- /dev/null +++ b/src/main/java/com/leisuretimedock/blasttravelreborn/network/toServer/StopCannonFlightServerPayload.java @@ -0,0 +1,47 @@ +package com.leisuretimedock.blasttravelreborn.network.toServer; + +import com.leisuretimedock.blasttravelreborn.network.BTRNetwork; +import com.leisuretimedock.blasttravelreborn.network.toClient.StopCannonFlightClientPayload; +import com.leisuretimedock.blasttravelreborn.util.PlayerEntityDuck; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraftforge.network.NetworkDirection; +import net.minecraftforge.network.NetworkEvent; + +import java.util.function.Supplier; + +public record StopCannonFlightServerPayload(boolean thud) { + public StopCannonFlightServerPayload(FriendlyByteBuf buf) { + this(buf.readBoolean()); + } + public void write(FriendlyByteBuf buf) { + buf.writeBoolean(thud); + } + public static StopCannonFlightServerPayload read(FriendlyByteBuf buf) { + return new StopCannonFlightServerPayload(buf); + } + public void handler(Supplier ctx) { + NetworkEvent.Context context = ctx.get(); + if(context.getNetworkManager().getPacketListener() instanceof ServerGamePacketListenerImpl) { + context.enqueueWork(() -> { + ServerPlayer sender = context.getSender(); + assert sender != null; + ((PlayerEntityDuck) sender).blasttravel$setCannonFlight(false); + if (sender.level() instanceof ServerLevel serverLevel) { + serverLevel.players().forEach(player -> BTRNetwork.CHANNEL.sendTo(new StopCannonFlightClientPayload(sender), player.connection.connection, NetworkDirection.PLAY_TO_CLIENT)); + } + if (thud) { + sender.level().playSound( + null ,sender.getX(), sender.getY(), sender.getZ(), + SoundEvents.GENERIC_SMALL_FALL, SoundSource.PLAYERS, 1, 0.78f + ); + } + }); + context.setPacketHandled(true); + } + } +} diff --git a/src/main/java/com/leisuretimedock/blasttravelreborn/util/BTRUtil.java b/src/main/java/com/leisuretimedock/blasttravelreborn/util/BTRUtil.java new file mode 100644 index 0000000..482a9e5 --- /dev/null +++ b/src/main/java/com/leisuretimedock/blasttravelreborn/util/BTRUtil.java @@ -0,0 +1,30 @@ +package com.leisuretimedock.blasttravelreborn.util; + +import com.leisuretimedock.blasttravelreborn.BlastTravelReborn; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; + +public enum BTRUtil {; + public static final ResourceLocation CANNON_OVERLAY_TEX = BlastTravelReborn.id("textures/misc/cannon_overlay.png"); + + public static void renderCannonHubOverlay(GuiGraphics gui, float scale) { + int scaledWidth = gui.guiWidth(), scaledHeight = gui.guiHeight(); + float f = (float)Math.min(scaledWidth, scaledHeight); + float g = f; + float h = Math.min((float)scaledWidth / f, (float)scaledHeight / g) * scale; + int i = Mth.floor(f * h); + int j = Mth.floor(g * h); + int k = (scaledWidth - i) / 2; + int l = (scaledHeight - j) / 2; + int m = k + i; + int n = l + j; + gui.blit(CANNON_OVERLAY_TEX, k, l, -90, 0.0F, 0.0F, i, j, i, j); + gui.fill(RenderType.guiOverlay(), 0, n, scaledWidth, scaledHeight, -90, -16777216); + gui.fill(RenderType.guiOverlay(), 0, 0, scaledWidth, l, -90, -16777216); + gui.fill(RenderType.guiOverlay(), 0, l, k, n, -90, -16777216); + gui.fill(RenderType.guiOverlay(), m, l, scaledWidth, n, -90, -16777216); + } + +} diff --git a/src/main/java/com/leisuretimedock/blasttravelreborn/util/PlayerEntityDuck.java b/src/main/java/com/leisuretimedock/blasttravelreborn/util/PlayerEntityDuck.java new file mode 100644 index 0000000..5219783 --- /dev/null +++ b/src/main/java/com/leisuretimedock/blasttravelreborn/util/PlayerEntityDuck.java @@ -0,0 +1,11 @@ +package com.leisuretimedock.blasttravelreborn.util; + +import net.minecraft.world.phys.Vec3; + +public interface PlayerEntityDuck { + void blasttravel$setCannonFlight(boolean inFlight); + + boolean blasttravel$inCannonFlight(); + + Vec3 blasttravel$getVelocityLerped(float delta); +} diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg new file mode 100644 index 0000000..18fcee5 --- /dev/null +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -0,0 +1,3 @@ +public net.minecraft.world.SimpleContainer f_19147_ # items +public net.minecraft.world.entity.item.FallingBlockEntity (Lnet/minecraft/world/level/Level;DDDLnet/minecraft/world/level/block/state/BlockState;)V # FallingBlockEntity +public net.minecraft.world.damagesource.DamageSources m_269298_(Lnet/minecraft/resources/ResourceKey;Lnet/minecraft/world/entity/Entity;)Lnet/minecraft/world/damagesource/DamageSource; # source \ No newline at end of file diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml new file mode 100644 index 0000000..1df0ef1 --- /dev/null +++ b/src/main/resources/META-INF/mods.toml @@ -0,0 +1,68 @@ +# 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="https://change.me.to.your.mods.homepage.example.invalid/" #optional +# A file name (in the root of the mod JAR) containing a logo for display +#logoFile="blasttravelreborn.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}"]] +versionRange = "[0.2.1+1.20-fabrge,)" +modId = "jsonem" +mandatory = false +side = "CLIENT" diff --git a/src/main/resources/assets/blasttravelreborn/icon.png b/src/main/resources/assets/blasttravelreborn/icon.png new file mode 100644 index 0000000..af99a81 Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/icon.png differ diff --git a/src/main/resources/assets/blasttravelreborn/lang/en_us.json b/src/main/resources/assets/blasttravelreborn/lang/en_us.json new file mode 100644 index 0000000..60b5516 --- /dev/null +++ b/src/main/resources/assets/blasttravelreborn/lang/en_us.json @@ -0,0 +1,10 @@ +{ + "container.blasttravelreborn.cannon_container_menu": "Cannon", + "item.blasttravelreborn.cannon": "Cannon", + "entity.blasttravelreborn.cannon": "Cannon", + + "death.attack.blasttravelreborn.cannon": "%s was knocked out by a flying %s", + "dialog.blasttravelreborn.full_cannon": "Cannon is full!", + "dialog.blasttravelreborn.no_gunpowder": "Cannon has no gunpowder!", + "mount.blasttravelreborn.cannon.onboard": "Press %s to Exit, or %s to Fire" +} diff --git a/src/main/resources/assets/blasttravelreborn/models/entity/cannon/main.json b/src/main/resources/assets/blasttravelreborn/models/entity/cannon/main.json new file mode 100644 index 0000000..65b8af9 --- /dev/null +++ b/src/main/resources/assets/blasttravelreborn/models/entity/cannon/main.json @@ -0,0 +1,1857 @@ +{ + "texture": { + "width": 64, + "height": 64 + }, + "bones": { + "main": { + "transform": { + "origin": [ + 0, + -1, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + -6, + -5, + -1 + ], + "dimensions": [ + 12, + 2, + 2 + ], + "uv": [ + 36, + 0 + ] + }, + { + "offset": [ + -4, + -6, + -3 + ], + "dimensions": [ + 8, + 4, + 6 + ], + "uv": [ + 36, + 54 + ] + } + ], + "children": { + "left_wheel": { + "transform": { + "origin": [ + 0, + -4, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + 6, + -1, + -1 + ], + "dimensions": [ + 2, + 2, + 2 + ], + "uv": [ + 0, + 22 + ] + }, + { + "offset": [ + 7, + -3.5, + -3.5 + ], + "dimensions": [ + 0, + 7, + 7 + ], + "uv": [ + 18, + 21 + ] + }, + { + "offset": [ + 6, + 3, + -2.05 + ], + "dimensions": [ + 2, + 2, + 4.125 + ], + "uv": [ + 0, + 0 + ] + } + ], + "children": { + "seg_1": { + "transform": { + "rotation": [ + -0.7853981633974483, + 0, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + 7, + -3.5, + -3.5 + ], + "dimensions": [ + 0, + 7, + 7 + ], + "uv": [ + 18, + 21 + ] + }, + { + "offset": [ + 6, + 3, + -2.05 + ], + "dimensions": [ + 2, + 2, + 4.125 + ], + "uv": [ + 0, + 0 + ] + } + ], + "children": {} + }, + "seg_2": { + "transform": { + "rotation": [ + -1.5707963267948966, + 0, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + 6, + 3, + -2.05 + ], + "dimensions": [ + 2, + 2, + 4.125 + ], + "uv": [ + 0, + 0 + ] + } + ], + "children": {} + }, + "seg_3": { + "transform": { + "rotation": [ + -2.356194490192345, + 0, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + 6, + 3, + -2.05 + ], + "dimensions": [ + 2, + 2, + 4.125 + ], + "uv": [ + 0, + 0 + ] + } + ], + "children": {} + }, + "seg_4": { + "transform": { + "rotation": [ + 3.141592653589793, + 0, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + 6, + 3, + -2.05 + ], + "dimensions": [ + 2, + 2, + 4.125 + ], + "uv": [ + 0, + 0 + ] + } + ], + "children": {} + }, + "seg_5": { + "transform": { + "rotation": [ + 2.356194490192345, + 0, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + 6, + 3, + -2.05 + ], + "dimensions": [ + 2, + 2, + 4.125 + ], + "uv": [ + 0, + 0 + ] + } + ], + "children": {} + }, + "seg_6": { + "transform": { + "rotation": [ + 1.5707963267948966, + 0, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + 6, + 3, + -2.05 + ], + "dimensions": [ + 2, + 2, + 4.125 + ], + "uv": [ + 0, + 0 + ] + } + ], + "children": {} + }, + "seg_7": { + "transform": { + "rotation": [ + 0.7853981633974483, + 0, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + 6, + 3, + -2.05 + ], + "dimensions": [ + 2, + 2, + 4.125 + ], + "uv": [ + 0, + 0 + ] + } + ], + "children": {} + } + } + }, + "right_wheel": { + "transform": { + "origin": [ + -14, + -4, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + 6, + -1, + -1 + ], + "dimensions": [ + 2, + 2, + 2 + ], + "uv": [ + 0, + 22 + ] + }, + { + "offset": [ + 7, + -3.5, + -3.5 + ], + "dimensions": [ + 0, + 7, + 7 + ], + "uv": [ + 18, + 21 + ] + }, + { + "offset": [ + 6, + 3, + -2.05 + ], + "dimensions": [ + 2, + 2, + 4.125 + ], + "uv": [ + 0, + 0 + ] + } + ], + "children": { + "seg_8": { + "transform": { + "rotation": [ + -0.7853981633974483, + 0, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + 7, + -3.5, + -3.5 + ], + "dimensions": [ + 0, + 7, + 7 + ], + "uv": [ + 18, + 21 + ] + }, + { + "offset": [ + 6, + 3, + -2.05 + ], + "dimensions": [ + 2, + 2, + 4.125 + ], + "uv": [ + 0, + 0 + ] + } + ], + "children": {} + }, + "seg_9": { + "transform": { + "rotation": [ + -1.5707963267948966, + 0, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + 6, + 3, + -2.05 + ], + "dimensions": [ + 2, + 2, + 4.125 + ], + "uv": [ + 0, + 0 + ] + } + ], + "children": {} + }, + "seg_10": { + "transform": { + "rotation": [ + -2.356194490192345, + 0, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + 6, + 3, + -2.05 + ], + "dimensions": [ + 2, + 2, + 4.125 + ], + "uv": [ + 0, + 0 + ] + } + ], + "children": {} + }, + "seg_11": { + "transform": { + "rotation": [ + 3.141592653589793, + 0, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + 6, + 3, + -2.05 + ], + "dimensions": [ + 2, + 2, + 4.125 + ], + "uv": [ + 0, + 0 + ] + } + ], + "children": {} + }, + "seg_12": { + "transform": { + "rotation": [ + 2.356194490192345, + 0, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + 6, + 3, + -2.05 + ], + "dimensions": [ + 2, + 2, + 4.125 + ], + "uv": [ + 0, + 0 + ] + } + ], + "children": {} + }, + "seg_13": { + "transform": { + "rotation": [ + 1.5707963267948966, + 0, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + 6, + 3, + -2.05 + ], + "dimensions": [ + 2, + 2, + 4.125 + ], + "uv": [ + 0, + 0 + ] + } + ], + "children": {} + }, + "seg_14": { + "transform": { + "rotation": [ + 0.7853981633974483, + 0, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + 6, + 3, + -2.05 + ], + "dimensions": [ + 2, + 2, + 4.125 + ], + "uv": [ + 0, + 0 + ] + } + ], + "children": {} + } + } + }, + "fire": { + "transform": { + "origin": [ + 0, + -12, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + -3, + -50, + -3 + ], + "dimensions": [ + 6, + 26, + 6 + ], + "uv": [ + 0, + 0 + ] + } + ], + "children": {} + }, + "player_head": { + "transform": { + "origin": [ + 0, + -12, + 0 + ] + }, + "cuboids": [], + "children": { + "head": { + "transform": { + "origin": [ + 0, + -19, + 0 + ], + "rotation": [ + -1.5707963267948966, + 0, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + -4, + -4, + -4 + ], + "dimensions": [ + 8, + 8, + 8 + ], + "dilation": [ + -0.5, + -0.5, + -0.5 + ], + "uv": [ + 0, + 0 + ] + }, + { + "name": "hat", + "offset": [ + -4, + -4, + -4 + ], + "dimensions": [ + 8, + 8, + 8 + ], + "uv": [ + 32, + 0 + ] + } + ], + "children": {} + } + } + }, + "cannon": { + "transform": { + "origin": [ + 0, + -11, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + -6, + -6, + -6 + ], + "dimensions": [ + 12, + 12, + 12 + ], + "uv": [ + 16, + 4 + ] + }, + { + "offset": [ + -5, + -21, + -5 + ], + "dimensions": [ + 10, + 15, + 10 + ], + "uv": [ + 24, + 28 + ] + }, + { + "offset": [ + -5.5, + -26, + -5.5 + ], + "dimensions": [ + 2, + 5, + 11 + ], + "uv": [ + 9, + 42 + ] + }, + { + "offset": [ + -3.5, + -26, + 3.5 + ], + "dimensions": [ + 7, + 5, + 2 + ], + "uv": [ + 0, + 33 + ] + }, + { + "offset": [ + -3.5, + -26, + -5.5 + ], + "dimensions": [ + 7, + 5, + 2 + ], + "uv": [ + 0, + 26 + ] + }, + { + "offset": [ + 3.5, + -26, + -5.5 + ], + "dimensions": [ + 2, + 5, + 11 + ], + "uv": [ + 2, + 0 + ] + }, + { + "offset": [ + -1.5, + -2, + 6 + ], + "dimensions": [ + 3, + 3, + 1 + ], + "uv": [ + 54, + 28 + ] + } + ], + "children": { + "chains": { + "cuboids": [], + "children": { + "chain_1": { + "transform": { + "origin": [ + -6, + 6, + 0 + ], + "rotation": [ + 0.03690386143228307, + -0.013428051350277146, + 0.3488180450794691 + ] + }, + "cuboids": [], + "children": { + "chain_a1": { + "transform": { + "origin": [ + 1.5, + 0, + 7 + ], + "rotation": [ + 0, + 0.5235987755982988, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + -1.5, + -12, + 0 + ], + "dimensions": [ + 3, + 12, + 0 + ], + "uv": [ + 14, + 40 + ] + }, + { + "offset": [ + -1.5, + -24, + 0 + ], + "dimensions": [ + 3, + 12, + 0 + ], + "uv": [ + 14, + 40 + ] + }, + { + "offset": [ + -1.5, + -30, + 0 + ], + "dimensions": [ + 3, + 6, + 0 + ], + "uv": [ + 14, + 40 + ] + } + ], + "children": {} + }, + "chain_b1": { + "transform": { + "origin": [ + 1.5, + 0, + 7 + ], + "rotation": [ + 0, + -0.5235987755982988, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + -1.5, + -9, + 0 + ], + "dimensions": [ + 3, + 10, + 0 + ], + "uv": [ + 14, + 40 + ] + }, + { + "offset": [ + -1.5, + -21, + 0 + ], + "dimensions": [ + 3, + 12, + 0 + ], + "uv": [ + 14, + 40 + ] + }, + { + "offset": [ + -1.5, + -27, + 0 + ], + "dimensions": [ + 3, + 6, + 0 + ], + "uv": [ + 14, + 40 + ] + } + ], + "children": {} + } + } + }, + "chain_2": { + "transform": { + "origin": [ + 3.099999999999998, + 6.899999999999998, + 0 + ], + "rotation": [ + 0.03690386143228307, + 0.013428563264846682, + -0.3488180136450814 + ] + }, + "cuboids": [], + "children": { + "chain_a2": { + "transform": { + "origin": [ + 1.5000000000000018, + 0, + 7 + ], + "rotation": [ + 0, + -0.5235987755982988, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + -1.5000000000000018, + -11.999999999999993, + 0 + ], + "dimensions": [ + 3.0000000000000018, + 11.999999999999993, + 0 + ], + "uv": [ + 14, + 40 + ] + }, + { + "offset": [ + -1.5000000000000018, + -23.999999999999993, + 0 + ], + "dimensions": [ + 3.0000000000000018, + 12, + 0 + ], + "uv": [ + 14, + 40 + ] + }, + { + "offset": [ + -1.5000000000000018, + -30, + 0 + ], + "dimensions": [ + 3.0000000000000018, + 6.000000000000007, + 0 + ], + "uv": [ + 14, + 40 + ] + } + ], + "children": {} + }, + "chain_b2": { + "transform": { + "origin": [ + 1.5000000000000018, + 0, + 7 + ], + "rotation": [ + 0, + 0.5235987755982988, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + -1.5000000000000018, + -8.999999999999996, + 0 + ], + "dimensions": [ + 3.0000000000000018, + 9.999999999999996, + 0 + ], + "uv": [ + 14, + 40 + ] + }, + { + "offset": [ + -1.5000000000000018, + -20.999999999999993, + 0 + ], + "dimensions": [ + 3.0000000000000018, + 11.999999999999995, + 0 + ], + "uv": [ + 14, + 40 + ] + }, + { + "offset": [ + -1.5000000000000018, + -26.999999999999993, + 0 + ], + "dimensions": [ + 3.0000000000000018, + 6, + 0 + ], + "uv": [ + 14, + 40 + ] + } + ], + "children": {} + } + } + }, + "chain_3": { + "transform": { + "origin": [ + -6, + 6, + -14 + ], + "rotation": [ + -0.03690324170416692, + -0.013428051350277146, + 0.3488180450794691 + ] + }, + "cuboids": [], + "children": { + "chain_a3": { + "transform": { + "origin": [ + 1.5, + 0, + 7 + ], + "rotation": [ + 0, + -0.5235987755982988, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + -1.5, + -12, + 0 + ], + "dimensions": [ + 3, + 12, + 0 + ], + "uv": [ + 14, + 40 + ] + }, + { + "offset": [ + -1.5, + -24, + 0 + ], + "dimensions": [ + 3, + 12, + 0 + ], + "uv": [ + 14, + 40 + ] + }, + { + "offset": [ + -1.5, + -30, + 0 + ], + "dimensions": [ + 3, + 6, + 0 + ], + "uv": [ + 14, + 40 + ] + } + ], + "children": {} + }, + "chain_b3": { + "transform": { + "origin": [ + 1.5, + 0, + 7 + ], + "rotation": [ + 0, + 0.5235987755982988, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + -1.5, + -9, + 0 + ], + "dimensions": [ + 3, + 10, + 0 + ], + "uv": [ + 14, + 40 + ] + }, + { + "offset": [ + -1.5, + -21, + 0 + ], + "dimensions": [ + 3, + 12, + 0 + ], + "uv": [ + 14, + 40 + ] + }, + { + "offset": [ + -1.5, + -27, + 0 + ], + "dimensions": [ + 3, + 6, + 0 + ], + "uv": [ + 14, + 40 + ] + } + ], + "children": {} + } + } + }, + "chain_4": { + "transform": { + "origin": [ + 3.099999999999998, + 6.899999999999998, + -14 + ], + "rotation": [ + -0.03690324170416692, + 0.013428563264846682, + -0.3488180136450814 + ] + }, + "cuboids": [], + "children": { + "chain_a4": { + "transform": { + "origin": [ + 1.5000000000000018, + 0, + 7 + ], + "rotation": [ + 0, + 0.5235987755982988, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + -1.5000000000000018, + -11.999999999999993, + 0 + ], + "dimensions": [ + 3.0000000000000018, + 11.999999999999993, + 0 + ], + "uv": [ + 14, + 40 + ] + }, + { + "offset": [ + -1.5000000000000018, + -23.999999999999993, + 0 + ], + "dimensions": [ + 3.0000000000000018, + 12, + 0 + ], + "uv": [ + 14, + 40 + ] + }, + { + "offset": [ + -1.5000000000000018, + -30, + 0 + ], + "dimensions": [ + 3.0000000000000018, + 6.000000000000007, + 0 + ], + "uv": [ + 14, + 40 + ] + } + ], + "children": {} + }, + "chain_b4": { + "transform": { + "origin": [ + 1.5000000000000018, + 0, + 7 + ], + "rotation": [ + 0, + -0.5235987755982988, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + -1.5000000000000018, + -8.999999999999996, + 0 + ], + "dimensions": [ + 3.0000000000000018, + 9.999999999999996, + 0 + ], + "uv": [ + 14, + 40 + ] + }, + { + "offset": [ + -1.5000000000000018, + -20.999999999999993, + 0 + ], + "dimensions": [ + 3.0000000000000018, + 11.999999999999995, + 0 + ], + "uv": [ + 14, + 40 + ] + }, + { + "offset": [ + -1.5000000000000018, + -26.999999999999993, + 0 + ], + "dimensions": [ + 3.0000000000000018, + 6, + 0 + ], + "uv": [ + 14, + 40 + ] + } + ], + "children": {} + } + } + }, + "chain_5": { + "transform": { + "origin": [ + 5.5, + -20, + 7 + ], + "rotation": [ + 1.5707963267948966, + 0, + 0 + ] + }, + "cuboids": [], + "children": { + "chain_a5": { + "transform": { + "origin": [ + 0.5, + 0, + 0 + ], + "rotation": [ + 0, + -1.0471975511965976, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + -1.5, + -12, + 0 + ], + "dimensions": [ + 3, + 12, + 0 + ], + "uv": [ + 14, + 40 + ] + } + ], + "children": {} + }, + "chain_b5": { + "transform": { + "origin": [ + 0.5, + 0, + 0 + ], + "rotation": [ + 0, + 1.0471975511965976, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + -1.5, + -9, + 0 + ], + "dimensions": [ + 3, + 8, + 0 + ], + "uv": [ + 14, + 40 + ] + }, + { + "offset": [ + -1.5, + -13, + 0 + ], + "dimensions": [ + 3, + 2, + 0 + ], + "uv": [ + 14, + 42 + ] + } + ], + "children": {} + } + } + }, + "chain_6": { + "transform": { + "origin": [ + -6.5, + -20, + 7 + ], + "rotation": [ + 1.5707963267948966, + 0, + 0 + ] + }, + "cuboids": [], + "children": { + "chain_a6": { + "transform": { + "origin": [ + 0.5, + 0, + 0 + ], + "rotation": [ + 0, + -1.0471975511965976, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + -1.5, + -12, + 0 + ], + "dimensions": [ + 3, + 12, + 0 + ], + "uv": [ + 14, + 40 + ] + } + ], + "children": {} + }, + "chain_b6": { + "transform": { + "origin": [ + 0.5, + 0, + 0 + ], + "rotation": [ + 0, + 1.0471975511965976, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + -1.5, + -9, + 0 + ], + "dimensions": [ + 3, + 8, + 0 + ], + "uv": [ + 14, + 40 + ] + }, + { + "offset": [ + -1.5, + -13, + 0 + ], + "dimensions": [ + 3, + 2, + 0 + ], + "uv": [ + 14, + 42 + ] + } + ], + "children": {} + } + } + }, + "chain_7": { + "transform": { + "origin": [ + -5.25, + 7, + 7 + ], + "rotation": [ + 1.5707963267948966, + 0, + 0 + ] + }, + "cuboids": [], + "children": { + "chain_a7": { + "transform": { + "origin": [ + 0.5, + 0, + 0 + ], + "rotation": [ + 0, + -0.5235987755982988, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + -1.5, + -12, + 0 + ], + "dimensions": [ + 3, + 12, + 0 + ], + "uv": [ + 14, + 40 + ] + } + ], + "children": {} + }, + "chain_b7": { + "transform": { + "origin": [ + 0.5, + 0, + 0 + ], + "rotation": [ + 0, + 0.5235987755982988, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + -1.5, + -9, + 0 + ], + "dimensions": [ + 3, + 9, + 0 + ], + "uv": [ + 14, + 40 + ] + }, + { + "offset": [ + -1.5, + -14, + 0 + ], + "dimensions": [ + 3, + 3, + 0 + ], + "uv": [ + 14, + 42 + ] + } + ], + "children": {} + } + } + }, + "chain_8": { + "transform": { + "origin": [ + 4.75, + 7, + 7 + ], + "rotation": [ + 1.5707963267948966, + 0, + 0 + ] + }, + "cuboids": [], + "children": { + "chain_a8": { + "transform": { + "origin": [ + 0.5, + 0, + 0 + ], + "rotation": [ + 0, + 0.5235987755982988, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + -1.5, + -12, + 0 + ], + "dimensions": [ + 3, + 12, + 0 + ], + "uv": [ + 14, + 40 + ] + } + ], + "children": {} + }, + "chain_b8": { + "transform": { + "origin": [ + 0.5, + 0, + 0 + ], + "rotation": [ + 0, + -0.5235987755982988, + 0 + ] + }, + "cuboids": [ + { + "offset": [ + -1.5, + -9, + 0 + ], + "dimensions": [ + 3, + 9, + 0 + ], + "uv": [ + 14, + 40 + ] + }, + { + "offset": [ + -1.5, + -14, + 0 + ], + "dimensions": [ + 3, + 3, + 0 + ], + "uv": [ + 14, + 42 + ] + } + ], + "children": {} + } + } + } + } + }, + "fuse": { + "transform": { + "origin": [ + 0, + -0.5, + 0 + ], + "rotation": [ + 0, + 0, + 0.7853981633974483 + ] + }, + "cuboids": [ + { + "offset": [ + 0, + -0.5, + 7 + ], + "dimensions": [ + 0, + 1, + 5 + ], + "uv": [ + 54, + 27 + ] + }, + { + "offset": [ + -0.5, + 0, + 7 + ], + "dimensions": [ + 1, + 0, + 5 + ], + "uv": [ + 49, + 33 + ] + } + ], + "children": {} + } + } + } + } + } + } +} diff --git a/src/main/resources/assets/blasttravelreborn/models/item/cannon.json b/src/main/resources/assets/blasttravelreborn/models/item/cannon.json new file mode 100644 index 0000000..c3484dc --- /dev/null +++ b/src/main/resources/assets/blasttravelreborn/models/item/cannon.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "blasttravelreborn:item/cannon" + } +} diff --git a/src/main/resources/assets/blasttravelreborn/particles/cannon_blast.json b/src/main/resources/assets/blasttravelreborn/particles/cannon_blast.json new file mode 100644 index 0000000..638be25 --- /dev/null +++ b/src/main/resources/assets/blasttravelreborn/particles/cannon_blast.json @@ -0,0 +1,13 @@ +{ + "textures": [ + "blasttravelreborn:blast_smoke_0", + "blasttravelreborn:blast_smoke_1", + "blasttravelreborn:blast_smoke_2", + "blasttravelreborn:blast_smoke_3", + "blasttravelreborn:blast_smoke_4", + "blasttravelreborn:blast_smoke_5", + "blasttravelreborn:blast_smoke_6", + "blasttravelreborn:blast_smoke_7", + "blasttravelreborn:blast_smoke_8" + ] +} diff --git a/src/main/resources/assets/blasttravelreborn/textures/entity/cannon/amethyst.png b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon/amethyst.png new file mode 100644 index 0000000..9190ce0 Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon/amethyst.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/entity/cannon/anvil.png b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon/anvil.png new file mode 100644 index 0000000..8ba2d57 Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon/anvil.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/entity/cannon/golden.png b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon/golden.png new file mode 100644 index 0000000..9414d2e Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon/golden.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/entity/cannon/head/powder.png b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon/head/powder.png new file mode 100644 index 0000000..31cce2c Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon/head/powder.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/entity/cannon/lazuli.png b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon/lazuli.png new file mode 100644 index 0000000..c0c4c33 Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon/lazuli.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/entity/cannon/mossy.png b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon/mossy.png new file mode 100644 index 0000000..0a4ca34 Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon/mossy.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/entity/cannon/regular.png b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon/regular.png new file mode 100644 index 0000000..cbd0e4e Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon/regular.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/entity/cannon/tnt.png b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon/tnt.png new file mode 100644 index 0000000..c5c3a67 Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon/tnt.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_fire/dyed_frame_0.png b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_fire/dyed_frame_0.png new file mode 100644 index 0000000..46922ac Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_fire/dyed_frame_0.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_fire/dyed_frame_1.png b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_fire/dyed_frame_1.png new file mode 100644 index 0000000..c6ad64a Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_fire/dyed_frame_1.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_fire/dyed_frame_2.png b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_fire/dyed_frame_2.png new file mode 100644 index 0000000..146dfa4 Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_fire/dyed_frame_2.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_fire/dyed_frame_3.png b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_fire/dyed_frame_3.png new file mode 100644 index 0000000..0166eb8 Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_fire/dyed_frame_3.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_fire/dyed_frame_4.png b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_fire/dyed_frame_4.png new file mode 100644 index 0000000..d77287d Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_fire/dyed_frame_4.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_fire/frame_0.png b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_fire/frame_0.png new file mode 100644 index 0000000..2823221 Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_fire/frame_0.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_fire/frame_1.png b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_fire/frame_1.png new file mode 100644 index 0000000..e9db4ef Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_fire/frame_1.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_fire/frame_2.png b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_fire/frame_2.png new file mode 100644 index 0000000..3bd1a44 Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_fire/frame_2.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_fire/frame_3.png b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_fire/frame_3.png new file mode 100644 index 0000000..0c2d9e5 Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_fire/frame_3.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_fire/frame_4.png b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_fire/frame_4.png new file mode 100644 index 0000000..64e1e3b Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_fire/frame_4.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_smoke/frame_0.png b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_smoke/frame_0.png new file mode 100644 index 0000000..1f33b40 Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_smoke/frame_0.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_smoke/frame_1.png b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_smoke/frame_1.png new file mode 100644 index 0000000..000af9b Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_smoke/frame_1.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_smoke/frame_2.png b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_smoke/frame_2.png new file mode 100644 index 0000000..447327f Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_smoke/frame_2.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_smoke/frame_3.png b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_smoke/frame_3.png new file mode 100644 index 0000000..59b62dc Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_smoke/frame_3.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_smoke/frame_4.png b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_smoke/frame_4.png new file mode 100644 index 0000000..1cba524 Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/entity/cannon_smoke/frame_4.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/gui/container/cannon.png b/src/main/resources/assets/blasttravelreborn/textures/gui/container/cannon.png new file mode 100644 index 0000000..2ed749b Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/gui/container/cannon.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/item/cannon.png b/src/main/resources/assets/blasttravelreborn/textures/item/cannon.png new file mode 100644 index 0000000..6c39d16 Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/item/cannon.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/misc/cannon_overlay.png b/src/main/resources/assets/blasttravelreborn/textures/misc/cannon_overlay.png new file mode 100644 index 0000000..2b9846f Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/misc/cannon_overlay.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/particle/blast_smoke_0.png b/src/main/resources/assets/blasttravelreborn/textures/particle/blast_smoke_0.png new file mode 100644 index 0000000..5af881f Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/particle/blast_smoke_0.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/particle/blast_smoke_1.png b/src/main/resources/assets/blasttravelreborn/textures/particle/blast_smoke_1.png new file mode 100644 index 0000000..333fba4 Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/particle/blast_smoke_1.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/particle/blast_smoke_2.png b/src/main/resources/assets/blasttravelreborn/textures/particle/blast_smoke_2.png new file mode 100644 index 0000000..6579c40 Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/particle/blast_smoke_2.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/particle/blast_smoke_3.png b/src/main/resources/assets/blasttravelreborn/textures/particle/blast_smoke_3.png new file mode 100644 index 0000000..a6f80ff Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/particle/blast_smoke_3.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/particle/blast_smoke_4.png b/src/main/resources/assets/blasttravelreborn/textures/particle/blast_smoke_4.png new file mode 100644 index 0000000..21a1c63 Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/particle/blast_smoke_4.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/particle/blast_smoke_5.png b/src/main/resources/assets/blasttravelreborn/textures/particle/blast_smoke_5.png new file mode 100644 index 0000000..25bc5eb Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/particle/blast_smoke_5.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/particle/blast_smoke_6.png b/src/main/resources/assets/blasttravelreborn/textures/particle/blast_smoke_6.png new file mode 100644 index 0000000..d0b5706 Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/particle/blast_smoke_6.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/particle/blast_smoke_7.png b/src/main/resources/assets/blasttravelreborn/textures/particle/blast_smoke_7.png new file mode 100644 index 0000000..626e8e3 Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/particle/blast_smoke_7.png differ diff --git a/src/main/resources/assets/blasttravelreborn/textures/particle/blast_smoke_8.png b/src/main/resources/assets/blasttravelreborn/textures/particle/blast_smoke_8.png new file mode 100644 index 0000000..0109183 Binary files /dev/null and b/src/main/resources/assets/blasttravelreborn/textures/particle/blast_smoke_8.png differ diff --git a/src/main/resources/blasttravelreborn.mixins.json b/src/main/resources/blasttravelreborn.mixins.json new file mode 100644 index 0000000..4abf844 --- /dev/null +++ b/src/main/resources/blasttravelreborn.mixins.json @@ -0,0 +1,21 @@ +{ + "required": true, + "minVersion": "0.8", + "refmap": "blasttravelreborn.refmap.json", + "package": "com.leisuretimedock.blasttravelreborn.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + "LivingEntityAccess", + "PlayerEntityMixin", + "PlayerEntityMixinClient" + ], + "client": [ + "ClientPlayerEntityMixin", + "ClientPlayNetworkHandlerMixin", + "PlayerEntityModelMixin", + "PlayerEntityRendererMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/src/main/resources/data/blasttravelreborn/damage_type/cannon.json b/src/main/resources/data/blasttravelreborn/damage_type/cannon.json new file mode 100644 index 0000000..5b2cafd --- /dev/null +++ b/src/main/resources/data/blasttravelreborn/damage_type/cannon.json @@ -0,0 +1,5 @@ +{ + "message_id": "cannon", + "exhaustion": 0, + "scaling": "when_caused_by_living_non_player" +} diff --git a/src/main/resources/data/blasttravelreborn/recipes/cannon.json b/src/main/resources/data/blasttravelreborn/recipes/cannon.json new file mode 100644 index 0000000..4746a98 --- /dev/null +++ b/src/main/resources/data/blasttravelreborn/recipes/cannon.json @@ -0,0 +1,19 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + " A", + "BA ", + "CDE" + ], + "key": { + "A": {"item": "minecraft:iron_block"}, + "B": {"item": "minecraft:string"}, + "C": {"tag": "minecraft:stone_crafting_materials"}, + "D": {"item": "minecraft:stick"}, + "E": {"tag": "minecraft:logs"} + }, + "result": { + "item": "blasttravelreborn:cannon", + "count": 1 + } +} diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta new file mode 100644 index 0000000..7dc8b99 --- /dev/null +++ b/src/main/resources/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "description": "blasttravelreborn resources", + "pack_format": 15 + } +}