commit 16f357aab06042dba6ce7c0e82ab24a9d59dcfee Author: TheBrokenRail Date: Wed Oct 14 21:21:12 2020 -0400 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3fb81fa --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# gradle + +.gradle/ +build/ +out/ +classes/ + +# idea + +.idea/ +*.iml +*.ipr +*.iws + +# vscode + +.settings/ +.vscode/ +bin/ +.classpath +.project + +# fabric + +run/ + +remappedSrc/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..141f1e4 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,4 @@ +# Changelog + +**1.0** +* Initial Release diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..1673e44 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,19 @@ +pipeline { + agent { + docker { + image 'openjdk:8-jdk' + } + } + stages { + stage('Build') { + steps { + sh './gradlew build' + } + post { + success { + archiveArtifacts artifacts: 'build/libs/*', fingerprint: true + } + } + } + } +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..886b254 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 TheBrokenRail + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..1732880 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# Herobrine: Rewoven +A fun Herobrine boss-fight for Fabric! + +This mod was created for [FallFest 1.16](https://modfest.net/fallfest/1.16/) + +## Changelog +[View Changelog](CHANGELOG.md) diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..0cc51cf --- /dev/null +++ b/build.gradle @@ -0,0 +1,65 @@ +plugins { + id 'fabric-loom' version '0.5-SNAPSHOT' +} + +compileJava { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +def mod_version = project.mod_version as Object +version = "${mod_version}+${project.minecraft_version}" +group = project.maven_group as Object + +minecraft { +} + +dependencies { + minecraft "com.mojang:minecraft:${project.minecraft_version}" + mappings "net.fabricmc:yarn:${project.minecraft_version}+build.${project.yarn_build}:v2" + modImplementation "net.fabricmc:fabric-loader:${project.fabric_loader_version}" + modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_api_version}" +} + +processResources { + inputs.property 'version', project.version + + from(sourceSets.main.resources.srcDirs) { + include 'fabric.mod.json' + expand 'version': project.version + } + + from(sourceSets.main.resources.srcDirs) { + exclude 'fabric.mod.json' + } +} + +// ensure that the encoding is set to UTF-8, no matter what the system default is +// this fixes some edge cases with special characters not displaying correctly +// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html +tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' +} + +// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task +// if it is present. +// If you remove this task, sources will not be generated. +task sourcesJar(type: Jar, dependsOn: classes) { + classifier 'sources' + from sourceSets.main.allSource + from sourceSets.main.resources +} + +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier 'javadoc' + from javadoc.destinationDir +} + +artifacts { + archives sourcesJar + archives javadocJar +} + +jar { + from "LICENSE" +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..68c607a --- /dev/null +++ b/gradle.properties @@ -0,0 +1,17 @@ +# Done to increase the memory available to gradle. +org.gradle.jvmargs = -Xmx1G + +# Fabric Properties + # check these on https://fabricmc.net/use + minecraft_version = 1.16.3 + simple_minecraft_version = 1.16.3 + yarn_build = 28 + fabric_loader_version = 0.10.1+build.209 + +# Mod Properties + mod_version = 1.0.0 + maven_group = com.thebrokenrail + +# Dependencies + # currently not on the main fabric site, check on the maven: https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api + fabric_api_version = 0.23.0+build.410-1.16 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..5c2d1cf 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..5baddfb --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sat Feb 29 21:58:32 EST 2020 +distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.2-all.zip +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..8e25e6c --- /dev/null +++ b/gradlew @@ -0,0 +1,188 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or 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 UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# 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"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# 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 + ;; + 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" + which java >/dev/null 2>&1 || 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 + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..9618d8d --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,100 @@ +@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=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@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%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +: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 %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="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! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..45a61de --- /dev/null +++ b/settings.gradle @@ -0,0 +1,12 @@ +pluginManagement { + repositories { + jcenter() + maven { + name = 'Fabric' + url = 'https://maven.fabricmc.net/' + } + gradlePluginPortal() + } +} + +rootProject.name = 'herobrine-rewoven' diff --git a/src/main/java/com/thebrokenrail/herobrine/HerobrineRewoven.java b/src/main/java/com/thebrokenrail/herobrine/HerobrineRewoven.java new file mode 100644 index 0000000..2d1f27a --- /dev/null +++ b/src/main/java/com/thebrokenrail/herobrine/HerobrineRewoven.java @@ -0,0 +1,26 @@ +package com.thebrokenrail.herobrine; + +import com.thebrokenrail.herobrine.entity.HerobrineEntity; +import com.thebrokenrail.herobrine.item.BlackDiamondItem; +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.object.builder.v1.entity.FabricDefaultAttributeRegistry; +import net.fabricmc.fabric.api.object.builder.v1.entity.FabricEntityTypeBuilder; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.SpawnGroup; +import net.minecraft.item.Item; +import net.minecraft.util.Identifier; +import net.minecraft.util.registry.Registry; + +public class HerobrineRewoven implements ModInitializer { + public static final String NAMESPACE = "herobrine-rewoven"; + + public static final Item BLACK_DIAMOND_ITEM = new BlackDiamondItem(); + public static final EntityType HEROBRINE_ENTITY_TYPE = FabricEntityTypeBuilder.create(SpawnGroup.MONSTER, HerobrineEntity::new).dimensions(EntityType.PLAYER.getDimensions()).build(); + + @Override + public void onInitialize() { + Registry.register(Registry.ITEM, new Identifier(NAMESPACE, "black_diamond"), BLACK_DIAMOND_ITEM); + Registry.register(Registry.ENTITY_TYPE, new Identifier(NAMESPACE, "herobrine"), HEROBRINE_ENTITY_TYPE); + FabricDefaultAttributeRegistry.register(HEROBRINE_ENTITY_TYPE, HerobrineEntity.createMobAttributes()); + } +} diff --git a/src/main/java/com/thebrokenrail/herobrine/client/HerobrineRewovenClient.java b/src/main/java/com/thebrokenrail/herobrine/client/HerobrineRewovenClient.java new file mode 100644 index 0000000..d6fd3ba --- /dev/null +++ b/src/main/java/com/thebrokenrail/herobrine/client/HerobrineRewovenClient.java @@ -0,0 +1,16 @@ +package com.thebrokenrail.herobrine.client; + +import com.thebrokenrail.herobrine.HerobrineRewoven; +import com.thebrokenrail.herobrine.client.entity.HerobrineEntityRenderer; +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.client.rendereregistry.v1.EntityRendererRegistry; + +@Environment(EnvType.CLIENT) +public class HerobrineRewovenClient implements ClientModInitializer { + @Override + public void onInitializeClient() { + EntityRendererRegistry.INSTANCE.register(HerobrineRewoven.HEROBRINE_ENTITY_TYPE, (entityRenderDispatcher, context) -> new HerobrineEntityRenderer(entityRenderDispatcher)); + } +} diff --git a/src/main/java/com/thebrokenrail/herobrine/client/entity/HerobrineEntityRenderer.java b/src/main/java/com/thebrokenrail/herobrine/client/entity/HerobrineEntityRenderer.java new file mode 100644 index 0000000..2c9a48c --- /dev/null +++ b/src/main/java/com/thebrokenrail/herobrine/client/entity/HerobrineEntityRenderer.java @@ -0,0 +1,81 @@ +package com.thebrokenrail.herobrine.client.entity; + +import com.thebrokenrail.herobrine.HerobrineRewoven; +import com.thebrokenrail.herobrine.entity.HerobrineEntity; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.entity.EntityRenderDispatcher; +import net.minecraft.client.render.entity.LivingEntityRenderer; +import net.minecraft.client.render.entity.feature.HeldItemFeatureRenderer; +import net.minecraft.client.render.entity.feature.StuckArrowsFeatureRenderer; +import net.minecraft.client.render.entity.model.PlayerEntityModel; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; + +@Environment(EnvType.CLIENT) +public class HerobrineEntityRenderer extends LivingEntityRenderer { + protected static class Model extends PlayerEntityModel { + public Model(float scale, boolean thinArms) { + super(scale, thinArms); + } + + @Override + protected void method_29353(HerobrineEntity livingEntity, float f) { + super.method_29353(livingEntity, f); + // Set Arm Angles + switch (livingEntity.getDataTracker().get(HerobrineEntity.POSE)) { + case FIREBALL: { + leftArm.pitch = -45; + leftArm.yaw = -6; + leftArm.roll = 0; + rightArm.pitch = -45; + rightArm.yaw = 6; + rightArm.roll = 0; + break; + } + case LIGHTNING: { + leftArm.roll = -128; + leftArm.yaw = 0; + leftArm.pitch = 0; + rightArm.roll = 210; + leftArm.yaw = 0; + rightArm.pitch = 0; + break; + } + case NORMAL: + default: { + leftArm.yaw = 0; + leftArm.roll = 0; + rightArm.yaw = 0; + rightArm.roll = 0; + break; + } + } + } + } + + public HerobrineEntityRenderer(EntityRenderDispatcher dispatcher) { + super(dispatcher, new Model(0.0f, false), 0.5f); + addFeature(new HeldItemFeatureRenderer<>(this)); + addFeature(new StuckArrowsFeatureRenderer<>(this)); + } + + @Override + public Identifier getTexture(HerobrineEntity entity) { + return new Identifier(HerobrineRewoven.NAMESPACE, "textures/entity/herobrine.png"); + } + + @Override + protected void renderLabelIfPresent(HerobrineEntity herobrineEntity, Text text, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i) { + matrixStack.push(); + super.renderLabelIfPresent(herobrineEntity, text, matrixStack, vertexConsumerProvider, i); + matrixStack.pop(); + } + + @Override + protected boolean hasLabel(HerobrineEntity livingEntity) { + return true; + } +} diff --git a/src/main/java/com/thebrokenrail/herobrine/config/HardcodedConfig.java b/src/main/java/com/thebrokenrail/herobrine/config/HardcodedConfig.java new file mode 100644 index 0000000..dbf4a28 --- /dev/null +++ b/src/main/java/com/thebrokenrail/herobrine/config/HardcodedConfig.java @@ -0,0 +1,38 @@ +package com.thebrokenrail.herobrine.config; + +import net.minecraft.entity.EntityType; + +public class HardcodedConfig { + public static final int HEROBRINE_HEALTH = 400; + public static final int HEROBRINE_PARTICLE_Y = -5; + public static final int HEROBRINE_SPAWN_Y = -90; + public static final int HEROBRINE_NO_PLAYER_TELEPORT_COUNTDOWN = 120; + public static int HEROBRINE_TELEPORT_TIME = 20; + public static final int HEROBRINE_FIREBALL_TIME = 10; + public static final int HEROBRINE_MINIMUM_IDLE_TIME = 40; + public static final double HEROBRINE_IDLE_END_CHANCE = 0.7d; + public static final float HEROBRINE_FOLLOW_ANGLE_THRESHOLD = 8; + public static final int HEROBRINE_RECOMMENDED_DISTANCE_CENTER = 8; + public static final int HEROBRINE_RECOMMENDED_DISTANCE_DIFF = 2; + public static final double HEROBRINE_FLY_SPEED = 0.06d; + public static final double HEROBRINE_FIREBALL_CHANCE = 0.5d; + public static final double HEROBRINE_LIGHTNING_CHANCE = 0.3d; + public static final int HEROBRINE_LIGHTNING_MIX_TIME = 120; + public static final int HEROBRINE_LIGHTNING_MAX_TIME = 240; + public static final int HEROBRINE_LIGHTNING_RANGE = 180; + public static final double HEROBRINE_LIGHTNING_ENTITY_CHANCE = 0.75d; + public static final EntityType[] HEROBRINE_LIGHTNING_ENTITIES = { + EntityType.ZOMBIE, + EntityType.SKELETON, + EntityType.CREEPER, + EntityType.SPIDER, + EntityType.HUSK, + EntityType.STRAY, + EntityType.CAVE_SPIDER, + EntityType.ENDERMAN, + EntityType.ZOMBIFIED_PIGLIN, + EntityType.ZOGLIN, + EntityType.ZOMBIE_VILLAGER + }; + public static final int HEROBRINE_MAX_DISTANCE = 60; +} diff --git a/src/main/java/com/thebrokenrail/herobrine/data/HerobrineData.java b/src/main/java/com/thebrokenrail/herobrine/data/HerobrineData.java new file mode 100644 index 0000000..373da82 --- /dev/null +++ b/src/main/java/com/thebrokenrail/herobrine/data/HerobrineData.java @@ -0,0 +1,72 @@ +package com.thebrokenrail.herobrine.data; + +import com.thebrokenrail.herobrine.HerobrineRewoven; +import com.thebrokenrail.herobrine.entity.HerobrineEntity; +import net.minecraft.entity.Entity; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.world.PersistentState; +import net.minecraft.world.World; + +import java.util.UUID; + +public class HerobrineData extends PersistentState { + private UUID uuid; + + private HerobrineData() { + super(HerobrineRewoven.NAMESPACE); + } + + public static HerobrineData get(ServerWorld world) { + if (world.getRegistryKey() != World.OVERWORLD) { + world = world.getServer().getWorld(World.OVERWORLD); + } + assert world != null; + return world.getPersistentStateManager().getOrCreate(HerobrineData::new, HerobrineRewoven.NAMESPACE); + } + + public void tick(ServerWorld world) { + if (uuid != null) { + Entity entity = world.getEntity(uuid); + if (entity instanceof HerobrineEntity) { + ((HerobrineEntity) entity).dataTick(); + } else { + uuid = null; + markDirty(); + } + } + } + + public boolean set(UUID uuid) { + if (uuid.equals(this.uuid)) { + return false; + } else if (this.uuid != null) { + return true; + } else { + this.uuid = uuid; + markDirty(); + return false; + } + } + + public HerobrineEntity get(World world) { + return (HerobrineEntity) ((ServerWorld) world).getEntity(uuid); + } + + @Override + public void fromTag(CompoundTag tag) { + if (uuid != null) { + tag.putUuid("UUID", uuid); + } + } + + @Override + public CompoundTag toTag(CompoundTag tag) { + if (tag.contains("UUID")) { + uuid = tag.getUuid("UUID"); + } else { + uuid = null; + } + return tag; + } +} diff --git a/src/main/java/com/thebrokenrail/herobrine/entity/HerobrineEntity.java b/src/main/java/com/thebrokenrail/herobrine/entity/HerobrineEntity.java new file mode 100644 index 0000000..05e555a --- /dev/null +++ b/src/main/java/com/thebrokenrail/herobrine/entity/HerobrineEntity.java @@ -0,0 +1,303 @@ +package com.thebrokenrail.herobrine.entity; + +import com.thebrokenrail.herobrine.HerobrineRewoven; +import com.thebrokenrail.herobrine.config.HardcodedConfig; +import com.thebrokenrail.herobrine.data.HerobrineData; +import com.thebrokenrail.herobrine.entity.ai.HerobrineAI; +import com.thebrokenrail.herobrine.entity.ai.stage.TeleportStage; +import net.minecraft.block.BlockState; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.attribute.DefaultAttributeContainer; +import net.minecraft.entity.attribute.EntityAttributes; +import net.minecraft.entity.boss.BossBar; +import net.minecraft.entity.boss.ServerBossBar; +import net.minecraft.entity.boss.WitherEntity; +import net.minecraft.entity.damage.DamageSource; +import net.minecraft.entity.data.DataTracker; +import net.minecraft.entity.data.TrackedData; +import net.minecraft.entity.data.TrackedDataHandler; +import net.minecraft.entity.data.TrackedDataHandlerRegistry; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.MessageType; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.network.packet.s2c.play.GameMessageS2CPacket; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.sound.SoundCategory; +import net.minecraft.sound.SoundEvent; +import net.minecraft.text.Text; +import net.minecraft.text.TranslatableText; +import net.minecraft.util.Arm; +import net.minecraft.util.Formatting; +import net.minecraft.util.Util; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.world.Difficulty; +import net.minecraft.world.GameRules; +import net.minecraft.world.World; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +@SuppressWarnings("EntityConstructor") +public class HerobrineEntity extends LivingEntity { + private static final TrackedDataHandler POSE_HANDLER = new TrackedDataHandler() { + @Override + public void write(PacketByteBuf data, Pose object) { + data.writeEnumConstant(object); + } + + @Override + public Pose read(PacketByteBuf packetByteBuf) { + return packetByteBuf.readEnumConstant(Pose.class); + } + + @Override + public Pose copy(Pose object) { + return object; + } + }; + static { + TrackedDataHandlerRegistry.register(POSE_HANDLER); + } + public static final TrackedData POSE = DataTracker.registerData(HerobrineEntity.class, POSE_HANDLER); + + private final ServerBossBar bossBar; + + private final List tracking = new ArrayList<>(); + private int noPlayerTeleportCountdown = HardcodedConfig.HEROBRINE_NO_PLAYER_TELEPORT_COUNTDOWN; + + private final HerobrineAI ai = new HerobrineAI(this); + + public HerobrineEntity(EntityType entityType, World world) { + super(entityType, world); + bossBar = new ServerBossBar(getDisplayName(), BossBar.Color.RED, BossBar.Style.PROGRESS); + setHealth(getMaxHealth()); + if (world.random.nextDouble() <= 0.001) { + setCustomName(new TranslatableText("entity." + HerobrineRewoven.NAMESPACE + ".herobrine/easter_egg")); + } + noClip = true; + } + + @Override + protected void initDataTracker() { + super.initDataTracker(); + getDataTracker().startTracking(POSE, Pose.NORMAL); + } + + @Override + public void setCustomName(Text name) { + super.setCustomName(name); + bossBar.setName(name); + } + + @Override + public Iterable getArmorItems() { + return Collections.emptyList(); + } + + @Override + public ItemStack getEquippedStack(EquipmentSlot slot) { + if (slot == EquipmentSlot.MAINHAND) { + return new ItemStack(Items.DIAMOND_SWORD); + } else { + return ItemStack.EMPTY; + } + } + + @Override + public void equipStack(EquipmentSlot slot, ItemStack stack) { + } + + @Override + public Arm getMainArm() { + return Arm.RIGHT; + } + + @Override + public boolean isInvulnerableTo(DamageSource damageSource) { + return damageSource == DamageSource.IN_WALL || super.isInvulnerableTo(damageSource); + } + + @Override + public boolean hasNoGravity() { + return true; + } + + public static DefaultAttributeContainer.Builder createMobAttributes() { + return LivingEntity.createLivingAttributes().add(EntityAttributes.GENERIC_ATTACK_DAMAGE, 1.0D).add(EntityAttributes.GENERIC_MOVEMENT_SPEED, 0.10000000149011612D).add(EntityAttributes.GENERIC_ATTACK_SPEED).add(EntityAttributes.GENERIC_LUCK).add(EntityAttributes.GENERIC_MAX_HEALTH, HardcodedConfig.HEROBRINE_HEALTH); + } + + private int destroyTime = 0; + + @Override + public void tick() { + super.tick(); + if (!getEntityWorld().isClient()) { + if (getEntityWorld().getDifficulty() == Difficulty.PEACEFUL || HerobrineData.get((ServerWorld) getEntityWorld()).set(getUuid())) { + // Herobrine Already Exists Or Is Peaceful + remove(); + } else { + ai.tick(); + + if (destroyTime > 0) { + destroyTime--; + if (destroyTime == 0 && getEntityWorld().getGameRules().getBoolean(GameRules.DO_MOB_GRIEFING)) { + int j = MathHelper.floor(this.getY()); + int n = MathHelper.floor(this.getX()); + int o = MathHelper.floor(this.getZ()); + boolean sendEvent = false; + + for (int p = -1; p <= 1; p++) { + for (int q = -1; q <= 1; q++) { + for (int r = 0; r <= 3; r++) { + int s = n + p; + int t = j + r; + int u = o + q; + BlockPos blockPos = new BlockPos(s, t, u); + BlockState blockState = getEntityWorld().getBlockState(blockPos); + if (WitherEntity.canDestroy(blockState)) { + sendEvent = getEntityWorld().breakBlock(blockPos, true, this) || sendEvent; + } + } + } + } + + if (sendEvent) { + getEntityWorld().syncWorldEvent(null, 1022, this.getBlockPos(), 0); + } + } + } else { + destroyTime = 20; + } + } + } + bossBar.setPercent(getHealth() / getMaxHealth()); + } + + public String getMessageKey(String id) { + return "entity." + HerobrineRewoven.NAMESPACE + ".herobrine.msg." + id; + } + + public void dataTick() { + boolean reset = false; + if (getAI().getStage() instanceof TeleportStage) { + reset = true; + } else { + if (tracking.size() > 0) { + reset = true; + } else if (noPlayerTeleportCountdown == 0) { + reset = true; + List players = getEntityWorld().getPlayers(); + if (players.size() > 0 || getNearestPlayer().distanceTo(this) >= HardcodedConfig.HEROBRINE_MAX_DISTANCE) { + while (true) { + PlayerEntity player = players.get(getEntityWorld().random.nextInt(players.size())); + if (player.isAlive()) { + getAI().setStage(new TeleportStage(this, player.getUuid())); + break; + } + } + } + } else { + noPlayerTeleportCountdown--; + } + } + if (reset) { + noPlayerTeleportCountdown = HardcodedConfig.HEROBRINE_NO_PLAYER_TELEPORT_COUNTDOWN; + } + } + + @Override + public void onStartedTrackingBy(ServerPlayerEntity player) { + super.onStartedTrackingBy(player); + bossBar.addPlayer(player); + tracking.add(player); + } + + @Override + public void onStoppedTrackingBy(ServerPlayerEntity player) { + super.onStoppedTrackingBy(player); + bossBar.removePlayer(player); + tracking.remove(player); + } + + public ServerPlayerEntity getNearestPlayer() { + tracking.sort((player1, player2) -> Float.compare(distanceTo(player1), distanceTo(player2))); + if (tracking.size() > 0) { + int i = 0; + while (true) { + if (tracking.get(i).isAlive()) { + return tracking.get(i); + } else { + i++; + } + } + } else { + return null; + } + } + + public static void summon(PlayerEntity player) { + HerobrineData data = HerobrineData.get((ServerWorld) player.getEntityWorld()); + HerobrineEntity entity = data.get(player.getEntityWorld()); + if (entity == null) { + ServerWorld world = Objects.requireNonNull(player.getEntityWorld().getServer()).getWorld(World.OVERWORLD); + entity = new HerobrineEntity(HerobrineRewoven.HEROBRINE_ENTITY_TYPE, world); + entity.updatePosition(0d, 51200d, 0d); + assert world != null; + Objects.requireNonNull(world.getServer().getWorld(World.OVERWORLD)).spawnEntity(entity); + data.set(entity.getUuid()); + world.getServer().getPlayerManager().sendToAll(new GameMessageS2CPacket(new TranslatableText("multiplayer.player.joined", entity.getDisplayName()).formatted(Formatting.YELLOW), MessageType.CHAT, Util.NIL_UUID)); + entity.getAI().setStage(new TeleportStage(entity, world.getSpawnPos())); + world.getServer().getPlayerManager().sendToAll(new GameMessageS2CPacket(new TranslatableText(entity.getMessageKey("spawn"), entity.getDisplayName()), MessageType.CHAT, Util.NIL_UUID)); + } + } + + public ServerWorld getServerWorld() { + return (ServerWorld) getEntityWorld(); + } + + public HerobrineAI getAI() { + return ai; + } + + public void handleStageChange() { + getDataTracker().set(POSE, Pose.NORMAL); + } + + @Override + public void writeCustomDataToTag(CompoundTag tag) { + super.writeCustomDataToTag(tag); + tag.put("AI", ai.toTag()); + tag.putInt("DestroyTime", destroyTime); + } + + @Override + public void readCustomDataFromTag(CompoundTag tag) { + super.readCustomDataFromTag(tag); + ai.fromTag(tag.getCompound("AI")); + destroyTime = tag.getInt("DestroyTime"); + } + + @Override + public void setRotation(float yaw, float pitch) { + super.setRotation(yaw, pitch); + } + + public void playSound(SoundEvent sound) { + getEntityWorld().playSound(null, getX(), getY(), getZ(), sound, SoundCategory.HOSTILE, 1.0f, 1.0f); + } + + public enum Pose { + NORMAL, + FIREBALL, + LIGHTNING + } +} diff --git a/src/main/java/com/thebrokenrail/herobrine/entity/ai/AIStage.java b/src/main/java/com/thebrokenrail/herobrine/entity/ai/AIStage.java new file mode 100644 index 0000000..e266438 --- /dev/null +++ b/src/main/java/com/thebrokenrail/herobrine/entity/ai/AIStage.java @@ -0,0 +1,43 @@ +package com.thebrokenrail.herobrine.entity.ai; + +import com.thebrokenrail.herobrine.entity.HerobrineEntity; +import net.minecraft.nbt.CompoundTag; + +public abstract class AIStage { + private String id; + private final HerobrineEntity entity; + + public AIStage(HerobrineEntity entity) { + this.entity = entity; + } + + public final String getID() { + return id; + } + final void setID(String id) { + this.id = id; + } + + protected HerobrineEntity getEntity() { + return entity; + } + + public final void nextStage() { + entity.getAI().nextStage(); + } + + public abstract void tick(); + public abstract void fromTag(CompoundTag tag); + public abstract CompoundTag toTag(); + + public static abstract class AIStageFactory { + public abstract boolean canStart(HerobrineEntity entity); + protected abstract T createInternal(HerobrineEntity entity); + public T create(HerobrineEntity entity) { + T obj = createInternal(entity); + obj.setID(getID()); + return obj; + } + public abstract String getID(); + } +} diff --git a/src/main/java/com/thebrokenrail/herobrine/entity/ai/HerobrineAI.java b/src/main/java/com/thebrokenrail/herobrine/entity/ai/HerobrineAI.java new file mode 100644 index 0000000..2ca09d6 --- /dev/null +++ b/src/main/java/com/thebrokenrail/herobrine/entity/ai/HerobrineAI.java @@ -0,0 +1,150 @@ +package com.thebrokenrail.herobrine.entity.ai; + +import com.thebrokenrail.herobrine.config.HardcodedConfig; +import com.thebrokenrail.herobrine.entity.HerobrineEntity; +import com.thebrokenrail.herobrine.entity.ai.stage.FireballStage; +import com.thebrokenrail.herobrine.entity.ai.stage.IdleStage; +import com.thebrokenrail.herobrine.entity.ai.stage.LightningStage; +import com.thebrokenrail.herobrine.entity.ai.stage.TeleportStage; +import net.minecraft.nbt.CompoundTag; + +import java.util.ArrayList; +import java.util.List; + +public class HerobrineAI { + private static final AIStage.AIStageFactory[] STAGES; + + private AIStage stage = null; + private final HerobrineEntity entity; + + public HerobrineAI(HerobrineEntity entity) { + this.entity = entity; + } + + public void tick() { + if (stage == null) { + nextStage(); + } + if (stage != null) { + stage.tick(); + } + } + + public void setStage(AIStage stage) { + this.stage = stage; + entity.handleStageChange(); + } + + public AIStage getStage() { + return stage; + } + + public void fromTag(CompoundTag tag) { + String id = tag.getString("ID"); + AIStage.AIStageFactory selected = null; + for (AIStage.AIStageFactory stage : STAGES) { + if (stage.getID().equals(id)) { + selected = stage; + break; + } + } + if (selected != null) { + setStage(selected.create(entity)); + stage.fromTag(tag.getCompound("Data")); + } else { + setStage(null); + } + } + + public CompoundTag toTag() { + CompoundTag tag = new CompoundTag(); + if (stage != null) { + tag.putString("ID", stage.getID()); + tag.put("Data", stage.toTag()); + } + return tag; + } + + public void nextStage() { + List> options = new ArrayList<>(); + for (AIStage.AIStageFactory stage : STAGES) { + if (stage.canStart(entity)) { + options.add(stage); + } + } + if (options.size() > 0) { + setStage(options.get(entity.getRandom().nextInt(options.size())).create(entity)); + } else { + setStage(null); + } + } + + static { + STAGES = new AIStage.AIStageFactory[]{ + new AIStage.AIStageFactory() { + @Override + public boolean canStart(HerobrineEntity entity) { + return false; + } + + @Override + protected AIStage createInternal(HerobrineEntity entity) { + return new TeleportStage(entity); + } + + @Override + public String getID() { + return "teleport"; + } + }, + new AIStage.AIStageFactory() { + @Override + public boolean canStart(HerobrineEntity entity) { + return entity.getRandom().nextDouble() <= HardcodedConfig.HEROBRINE_FIREBALL_CHANCE; + } + + @Override + protected AIStage createInternal(HerobrineEntity entity) { + return new FireballStage(entity); + } + + @Override + public String getID() { + return "fireball"; + } + }, + new AIStage.AIStageFactory() { + @Override + public boolean canStart(HerobrineEntity entity) { + return true; + } + + @Override + protected AIStage createInternal(HerobrineEntity entity) { + return new IdleStage(entity); + } + + @Override + public String getID() { + return "idle"; + } + }, + new AIStage.AIStageFactory() { + @Override + public boolean canStart(HerobrineEntity entity) { + return entity.getRandom().nextDouble() <= HardcodedConfig.HEROBRINE_LIGHTNING_CHANCE && (entity.getEntityWorld().isSkyVisible(entity.getBlockPos()) || entity.getEntityWorld().isSkyVisible(entity.getBlockPos().up())); + } + + @Override + protected AIStage createInternal(HerobrineEntity entity) { + return new LightningStage(entity); + } + + @Override + public String getID() { + return "lightning"; + } + } + }; + } +} diff --git a/src/main/java/com/thebrokenrail/herobrine/entity/ai/stage/FireballStage.java b/src/main/java/com/thebrokenrail/herobrine/entity/ai/stage/FireballStage.java new file mode 100644 index 0000000..e2a8797 --- /dev/null +++ b/src/main/java/com/thebrokenrail/herobrine/entity/ai/stage/FireballStage.java @@ -0,0 +1,83 @@ +package com.thebrokenrail.herobrine.entity.ai.stage; + +import com.thebrokenrail.herobrine.config.HardcodedConfig; +import com.thebrokenrail.herobrine.entity.HerobrineEntity; +import com.thebrokenrail.herobrine.entity.ai.AIStage; +import net.minecraft.entity.Entity; +import net.minecraft.entity.TntEntity; +import net.minecraft.entity.projectile.ExplosiveProjectileEntity; +import net.minecraft.entity.projectile.FireballEntity; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.sound.SoundEvents; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.GameRules; + +public class FireballStage extends AIStage { + private int time = 0; + private Entity entity = null; + + public FireballStage(HerobrineEntity entity) { + super(entity); + } + + @Override + public void tick() { + getEntity().getDataTracker().set(HerobrineEntity.POSE, HerobrineEntity.Pose.FIREBALL); + + if (entity != null) { + updatePosition(); + } + if (time < HardcodedConfig.HEROBRINE_FIREBALL_TIME) { + time++; + } else if (entity == null) { + time = 0; + + if (getEntity().getRandom().nextBoolean() && getEntity().getEntityWorld().getGameRules().getBoolean(GameRules.DO_MOB_GRIEFING)) { + entity = new TntEntity(getEntity().getEntityWorld(), 0, 0, 0, getEntity()); + getEntity().playSound(SoundEvents.ITEM_FLINTANDSTEEL_USE); + } else { + entity = new FireballEntity(getEntity().getEntityWorld(), getEntity(), 0, 0, 0); + ((FireballEntity) entity).explosionPower = 4; + getEntity().playSound(SoundEvents.ENTITY_GHAST_SHOOT); + } + entity.setNoGravity(true); + + updatePosition(); + + getEntity().getEntityWorld().spawnEntity(entity); + } else { + time = 0; + + Vec3d pos = getEntity().getRotationVec(1.0f); + entity.setVelocity(pos); + if (entity instanceof FireballEntity) { + ((ExplosiveProjectileEntity) entity).posX = pos.getX() * 0.3d; + ((ExplosiveProjectileEntity) entity).posY = pos.getY() * 0.3d; + ((ExplosiveProjectileEntity) entity).posZ = pos.getZ() * 0.3d; + } + entity.setNoGravity(false); + + nextStage(); + } + } + + private void updatePosition() { + Vec3d pos = getEntity().getRotationVec(1.0f).add(getEntity().getPos()); + entity.updatePosition(pos.getX(), pos.getY(), pos.getZ()); + entity.setVelocity(0, 0, 0); + } + + @Override + public void fromTag(CompoundTag tag) { + time = tag.getInt("Time"); + entity = getEntity().getServerWorld().getEntity(tag.getUuid("Entity")); + } + + @Override + public CompoundTag toTag() { + CompoundTag tag = new CompoundTag(); + tag.putUuid("Entity", entity.getUuid()); + tag.putInt("Time", time); + return tag; + } +} diff --git a/src/main/java/com/thebrokenrail/herobrine/entity/ai/stage/IdleStage.java b/src/main/java/com/thebrokenrail/herobrine/entity/ai/stage/IdleStage.java new file mode 100644 index 0000000..5df2093 --- /dev/null +++ b/src/main/java/com/thebrokenrail/herobrine/entity/ai/stage/IdleStage.java @@ -0,0 +1,118 @@ +package com.thebrokenrail.herobrine.entity.ai.stage; + +import com.thebrokenrail.herobrine.config.HardcodedConfig; +import com.thebrokenrail.herobrine.entity.HerobrineEntity; +import com.thebrokenrail.herobrine.entity.ai.AIStage; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; + +public class IdleStage extends AIStage { + private int time = 0; + + public IdleStage(HerobrineEntity entity) { + super(entity); + } + + @Override + public void tick() { + if (time < HardcodedConfig.HEROBRINE_MINIMUM_IDLE_TIME) { + time++; + } else if (getEntity().getRandom().nextDouble() < HardcodedConfig.HEROBRINE_IDLE_END_CHANCE) { + nextStage(); + return; + } + + ServerPlayerEntity nearest = getEntity().getNearestPlayer(); + + boolean moving = false; + if (nearest != null) { + float maxDiff = Math.abs(target(getTargetYaw(nearest.getPos()), getTargetPitch(nearest.getPos()))); + + if (maxDiff < HardcodedConfig.HEROBRINE_FOLLOW_ANGLE_THRESHOLD) { + Vec3d diff = getEntity().getRotationVec(1.0f); + diff = diff.multiply(HardcodedConfig.HEROBRINE_FLY_SPEED); + + float distance = Math.abs(getEntity().distanceTo(nearest)); + boolean go = false; + if (distance > (HardcodedConfig.HEROBRINE_RECOMMENDED_DISTANCE_CENTER + HardcodedConfig.HEROBRINE_RECOMMENDED_DISTANCE_DIFF)) { + go = true; + } else if (distance < (HardcodedConfig.HEROBRINE_RECOMMENDED_DISTANCE_CENTER - HardcodedConfig.HEROBRINE_RECOMMENDED_DISTANCE_DIFF)) { + go = true; + // Prefer Moving On X And Z When Backing Away + diff = new Vec3d(diff.getX(), 0, diff.getZ()).multiply(-1); + } + if (go) { + getEntity().addVelocity(diff.getX(), diff.getY(), diff.getZ()); + moving = true; + } + } + } + if (!moving) { + getEntity().setVelocity(getEntity().getVelocity().multiply(0.25d)); + getEntity().velocityDirty = true; + } + } + + private Vec3d getPosVec() { + return getEntity().getPos(); + } + + private static final double RAD2DEG = 180d / Math.PI; + + private float getTargetPitch(Vec3d targetPos) { + Vec3d pos = getPosVec(); + double diffX = targetPos.getX() - pos.getX(); + double diffY = targetPos.getY() - pos.getY(); + double diffZ = targetPos.getZ() - pos.getZ(); + double g = MathHelper.sqrt(diffX * diffX + diffZ * diffZ); + return (float) -(MathHelper.atan2(diffY, g) * RAD2DEG); + } + + private float getTargetYaw(Vec3d targetPos) { + Vec3d pos = getPosVec(); + double diffX = targetPos.getX() - pos.getX(); + double diffZ = targetPos.getZ() - pos.getZ(); + return (float) (MathHelper.atan2(diffZ, diffX) * RAD2DEG) - 90f; + } + + private static final float ROTATION_INCREMENT = 4; + + private void change(float diffYaw, float diffPitch) { + getEntity().yaw = diffYaw; + getEntity().headYaw = diffYaw; + getEntity().pitch = diffPitch; + } + + private float changeAngle(float from, float to, float max) { + float f = MathHelper.subtractAngles(from, to); + float g = MathHelper.clamp(f, -max, max); + return from + g; + } + + private float[] getAngleChange(float from, float to) { + float diff = MathHelper.subtractAngles(from, to); + float diff2 = changeAngle(from, to, ROTATION_INCREMENT); + return new float[]{diff2, diff}; + } + + private float target(float targetYaw, float targetPitch) { + float[] yawChange = getAngleChange(getEntity().headYaw, targetYaw); + float[] pitchChange = getAngleChange(getEntity().pitch, targetPitch); + change(yawChange[0], pitchChange[0]); + return Math.max(yawChange[1], pitchChange[1]); + } + + @Override + public void fromTag(CompoundTag tag) { + time = tag.getInt("Time"); + } + + @Override + public CompoundTag toTag() { + CompoundTag tag = new CompoundTag(); + tag.putInt("Time", time); + return tag; + } +} diff --git a/src/main/java/com/thebrokenrail/herobrine/entity/ai/stage/LightningStage.java b/src/main/java/com/thebrokenrail/herobrine/entity/ai/stage/LightningStage.java new file mode 100644 index 0000000..ea884d2 --- /dev/null +++ b/src/main/java/com/thebrokenrail/herobrine/entity/ai/stage/LightningStage.java @@ -0,0 +1,71 @@ +package com.thebrokenrail.herobrine.entity.ai.stage; + +import com.thebrokenrail.herobrine.config.HardcodedConfig; +import com.thebrokenrail.herobrine.entity.HerobrineEntity; +import com.thebrokenrail.herobrine.entity.ai.AIStage; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LightningEntity; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.Heightmap; + +public class LightningStage extends AIStage { + private int time = 0; + private int max_time; + + public LightningStage(HerobrineEntity entity) { + super(entity); + max_time = getEntity().getRandom().nextInt((HardcodedConfig.HEROBRINE_LIGHTNING_MAX_TIME + 1) - HardcodedConfig.HEROBRINE_LIGHTNING_MIX_TIME) + HardcodedConfig.HEROBRINE_LIGHTNING_MIX_TIME; + } + + @Override + public void tick() { + if (time >= max_time) { + nextStage(); + } else { + time++; + + getEntity().getDataTracker().set(HerobrineEntity.POSE, HerobrineEntity.Pose.LIGHTNING); + + if ((time & 2) == 0) { + int x = getEntity().getRandom().nextInt(HardcodedConfig.HEROBRINE_LIGHTNING_RANGE + 1); + int z = getEntity().getRandom().nextInt(HardcodedConfig.HEROBRINE_LIGHTNING_RANGE + 1); + x = x - HardcodedConfig.HEROBRINE_LIGHTNING_RANGE / 2; + z = z - HardcodedConfig.HEROBRINE_LIGHTNING_RANGE / 2; + x = x + (int) getEntity().getX(); + z = z + (int) getEntity().getZ(); + int y = getEntity().getEntityWorld().getTopY(Heightmap.Type.WORLD_SURFACE, x, z); + + Vec3d pos = new Vec3d(x + 0.5d, y, z + 0.5d); + + LightningEntity lightning = new LightningEntity(EntityType.LIGHTNING_BOLT, getEntity().getEntityWorld()); + lightning.setCosmetic(true); + lightning.updatePosition(pos.getX(), pos.getY(), pos.getZ()); + getEntity().getEntityWorld().spawnEntity(lightning); + + if (getEntity().getRandom().nextDouble() <= HardcodedConfig.HEROBRINE_LIGHTNING_ENTITY_CHANCE) { + EntityType entityType = HardcodedConfig.HEROBRINE_LIGHTNING_ENTITIES[getEntity().getRandom().nextInt(HardcodedConfig.HEROBRINE_LIGHTNING_ENTITIES.length)]; + Entity entity = entityType.create(getEntity().getEntityWorld()); + assert entity != null; + entity.updatePosition(pos.getX(), pos.getY(), pos.getZ()); + getEntity().getEntityWorld().spawnEntity(entity); + } + } + } + } + + @Override + public void fromTag(CompoundTag tag) { + time = tag.getInt("Time"); + max_time = tag.getInt("MaxTime"); + } + + @Override + public CompoundTag toTag() { + CompoundTag data = new CompoundTag(); + data.putInt("Time", time); + data.putInt("MaxTime", max_time); + return data; + } +} diff --git a/src/main/java/com/thebrokenrail/herobrine/entity/ai/stage/TeleportStage.java b/src/main/java/com/thebrokenrail/herobrine/entity/ai/stage/TeleportStage.java new file mode 100644 index 0000000..cb263b7 --- /dev/null +++ b/src/main/java/com/thebrokenrail/herobrine/entity/ai/stage/TeleportStage.java @@ -0,0 +1,82 @@ +package com.thebrokenrail.herobrine.entity.ai.stage; + +import com.thebrokenrail.herobrine.config.HardcodedConfig; +import com.thebrokenrail.herobrine.entity.HerobrineEntity; +import com.thebrokenrail.herobrine.entity.ai.AIStage; +import net.minecraft.entity.Entity; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.MessageType; +import net.minecraft.network.packet.s2c.play.GameMessageS2CPacket; +import net.minecraft.particle.ParticleTypes; +import net.minecraft.sound.SoundCategory; +import net.minecraft.sound.SoundEvents; +import net.minecraft.text.TranslatableText; +import net.minecraft.util.Util; +import net.minecraft.util.math.BlockPos; + +import java.util.Objects; +import java.util.UUID; + +public class TeleportStage extends AIStage { + private int time = 0; + private UUID target = null; + private BlockPos targetPos = null; + + public TeleportStage(HerobrineEntity entity) { + super(entity); + } + + public TeleportStage(HerobrineEntity entity, UUID target) { + this(entity); + this.target = target; + } + + public TeleportStage(HerobrineEntity entity, BlockPos targetPos) { + this(entity); + this.targetPos = targetPos; + } + + @Override + public void tick() { + if (time < HardcodedConfig.HEROBRINE_TELEPORT_TIME) { + time++; + getEntity().getServerWorld().spawnParticles(ParticleTypes.PORTAL, getEntity().getX(), getEntity().getRandomBodyY() - 0.25d, getEntity().getZ(), 6, 0.2d, 0.2d, 0.2d, 0.4d); + } else { + Entity targetEntity = getEntity().getServerWorld().getEntity(target); + if (targetEntity != null) { + getEntity().playSound(SoundEvents.ITEM_CHORUS_FRUIT_TELEPORT); + getEntity().updatePosition(targetEntity.getX() + 0.5d, targetEntity.getY(), targetEntity.getZ() + 0.5d); + getEntity().playSound(SoundEvents.ITEM_CHORUS_FRUIT_TELEPORT); + Objects.requireNonNull(getEntity().getEntityWorld().getServer()).getPlayerManager().sendToAll(new GameMessageS2CPacket(new TranslatableText(getEntity().getMessageKey("hello"), getEntity().getDisplayName(), targetEntity.getDisplayName()), MessageType.CHAT, Util.NIL_UUID)); + } + nextStage(); + } + } + + @Override + public void fromTag(CompoundTag tag) { + time = tag.getInt("Time"); + if (!tag.contains("Target")) { + int x = tag.getInt("X"); + int y = tag.getInt("Y"); + int z = tag.getInt("X"); + targetPos = new BlockPos(x, y, z); + } else { + target = tag.getUuid("Target"); + } + } + + @Override + public CompoundTag toTag() { + CompoundTag tag = new CompoundTag(); + tag.putInt("Time", time); + if (targetPos != null) { + tag.putInt("X", targetPos.getX()); + tag.putInt("Y", targetPos.getY()); + tag.putInt("Z", targetPos.getZ()); + } else { + tag.putUuid("Target", target); + } + return tag; + } +} diff --git a/src/main/java/com/thebrokenrail/herobrine/item/BlackDiamondItem.java b/src/main/java/com/thebrokenrail/herobrine/item/BlackDiamondItem.java new file mode 100644 index 0000000..a0930db --- /dev/null +++ b/src/main/java/com/thebrokenrail/herobrine/item/BlackDiamondItem.java @@ -0,0 +1,27 @@ +package com.thebrokenrail.herobrine.item; + +import com.thebrokenrail.herobrine.HerobrineRewoven; +import net.minecraft.client.item.TooltipContext; +import net.minecraft.item.Item; +import net.minecraft.item.ItemGroup; +import net.minecraft.item.ItemStack; +import net.minecraft.text.Text; +import net.minecraft.text.TranslatableText; +import net.minecraft.util.Formatting; +import net.minecraft.util.Rarity; +import net.minecraft.world.World; + +import java.util.List; + +public class BlackDiamondItem extends Item { + public BlackDiamondItem() { + super(new Settings().group(ItemGroup.MISC).maxCount(1).rarity(Rarity.RARE).fireproof()); + } + + @Override + public void appendTooltip(ItemStack stack, World world, List tooltip, TooltipContext context) { + super.appendTooltip(stack, world, tooltip, context); + tooltip.add(new TranslatableText("item." + HerobrineRewoven.NAMESPACE + ".black_diamond.tooltip.1").formatted(Formatting.GRAY).formatted(Formatting.ITALIC)); + tooltip.add(new TranslatableText("item." + HerobrineRewoven.NAMESPACE + ".black_diamond.tooltip.2").formatted(Formatting.GRAY).formatted(Formatting.ITALIC)); + } +} diff --git a/src/main/java/com/thebrokenrail/herobrine/mixin/MixinPlayerEntity.java b/src/main/java/com/thebrokenrail/herobrine/mixin/MixinPlayerEntity.java new file mode 100644 index 0000000..5b8a207 --- /dev/null +++ b/src/main/java/com/thebrokenrail/herobrine/mixin/MixinPlayerEntity.java @@ -0,0 +1,43 @@ +package com.thebrokenrail.herobrine.mixin; + +import com.thebrokenrail.herobrine.HerobrineRewoven; +import com.thebrokenrail.herobrine.config.HardcodedConfig; +import com.thebrokenrail.herobrine.entity.HerobrineEntity; +import net.minecraft.entity.damage.DamageSource; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.particle.ParticleTypes; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.sound.SoundCategory; +import net.minecraft.sound.SoundEvents; +import net.minecraft.util.Hand; +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; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(PlayerEntity.class) +public class MixinPlayerEntity { + @Inject(at = @At("HEAD"), method = "isInvulnerableTo", cancellable = true) + public void isInvulnerableTo(DamageSource damageSource, CallbackInfoReturnable info) { + PlayerEntity player = (PlayerEntity) (Object) this; + if (damageSource == DamageSource.OUT_OF_WORLD && player.isHolding(HerobrineRewoven.BLACK_DIAMOND_ITEM)) { + info.setReturnValue(true); + } + } + + @Inject(at = @At("RETURN"), method = "tick") + public void tick(CallbackInfo info) { + PlayerEntity player = (PlayerEntity) (Object) this; + if (player.isAlive() && !player.getEntityWorld().isClient() && player.isHolding(HerobrineRewoven.BLACK_DIAMOND_ITEM) && player.getY() <= HardcodedConfig.HEROBRINE_PARTICLE_Y) { + ((ServerWorld) player.getEntityWorld()).spawnParticles(ParticleTypes.DRAGON_BREATH, player.getX(), player.getY() - 0.5d, player.getZ(), 64, 0.4d, 0.4d, 0.4d, 0d); + if (player.getY() <= HardcodedConfig.HEROBRINE_SPAWN_Y) { + HerobrineEntity.summon(player); + boolean isMainHand = player.getStackInHand(Hand.MAIN_HAND).getItem() == HerobrineRewoven.BLACK_DIAMOND_ITEM; + player.setStackInHand(isMainHand ? Hand.MAIN_HAND : Hand.OFF_HAND, ItemStack.EMPTY); + player.getEntityWorld().playSound(null, player.getX(), player.getY(), player.getZ(), SoundEvents.BLOCK_END_PORTAL_SPAWN, SoundCategory.PLAYERS, 1.0f, 1.0f); + } + } + } +} diff --git a/src/main/java/com/thebrokenrail/herobrine/mixin/MixinServerWorld.java b/src/main/java/com/thebrokenrail/herobrine/mixin/MixinServerWorld.java new file mode 100644 index 0000000..2ae5fc5 --- /dev/null +++ b/src/main/java/com/thebrokenrail/herobrine/mixin/MixinServerWorld.java @@ -0,0 +1,23 @@ +package com.thebrokenrail.herobrine.mixin; + +import com.thebrokenrail.herobrine.data.HerobrineData; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.world.World; +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; + +import java.util.function.BooleanSupplier; + +@Mixin(ServerWorld.class) +public class MixinServerWorld { + @Inject(at = @At("HEAD"), method = "tick") + public void tick(BooleanSupplier shouldKeepTicking, CallbackInfo info) { + ServerWorld world = ((ServerWorld) (Object) this); + if (world.getRegistryKey() == World.OVERWORLD) { + HerobrineData data = HerobrineData.get(world); + data.tick(world); + } + } +} diff --git a/src/main/resources/assets/herobrine-rewoven/lang/en_us.json b/src/main/resources/assets/herobrine-rewoven/lang/en_us.json new file mode 100644 index 0000000..4734a3c --- /dev/null +++ b/src/main/resources/assets/herobrine-rewoven/lang/en_us.json @@ -0,0 +1,9 @@ +{ + "item.herobrine-rewoven.black_diamond": "Black Diamond", + "item.herobrine-rewoven.black_diamond.tooltip.1": "To Summon The Darkness,", + "item.herobrine-rewoven.black_diamond.tooltip.2": "You Must Cast It Away", + "entity.herobrine-rewoven.herobrine": "Herobrine", + "entity.herobrine-rewoven.herobrine.easter_egg": "Herobrian", + "entity.herobrine-rewoven.herobrine.msg.spawn": "<%s> I have been trapped for §k1000§r years! NOW, I AM FINALLY FREE!", + "entity.herobrine-rewoven.herobrine.msg.hello": "<%s> Hello, %s. Time to die!" +} \ No newline at end of file diff --git a/src/main/resources/assets/herobrine-rewoven/models/item/black_diamond.json b/src/main/resources/assets/herobrine-rewoven/models/item/black_diamond.json new file mode 100644 index 0000000..03f29cd --- /dev/null +++ b/src/main/resources/assets/herobrine-rewoven/models/item/black_diamond.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "herobrine-rewoven:item/black_diamond" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/herobrine-rewoven/textures/entity/herobrine.png b/src/main/resources/assets/herobrine-rewoven/textures/entity/herobrine.png new file mode 100644 index 0000000..b8910fe Binary files /dev/null and b/src/main/resources/assets/herobrine-rewoven/textures/entity/herobrine.png differ diff --git a/src/main/resources/assets/herobrine-rewoven/textures/item/black_diamond.png b/src/main/resources/assets/herobrine-rewoven/textures/item/black_diamond.png new file mode 100644 index 0000000..f66deb1 Binary files /dev/null and b/src/main/resources/assets/herobrine-rewoven/textures/item/black_diamond.png differ diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..b4c6bd0 --- /dev/null +++ b/src/main/resources/fabric.mod.json @@ -0,0 +1,34 @@ +{ + "schemaVersion": 1, + "id": "herobrine-rewoven", + "version": "${version}", + "name": "Herobrine: Rewoven", + "description": "A fun Herobrine boss-fight for Fabric!", + "authors": [ + "TheBrokenRail" + ], + "contact": { + "homepage": "https://thebrokenrail.com/", + "sources": "https://gitea.thebrokenrail.com/TheBrokenRail/Herobrine-Rewoven.git", + "issues": "https://gitea.thebrokenrail.com/TheBrokenRail/Herobrine-Rewoven/issues" + }, + "license": "MIT", + "icon": "assets/herobrine-rewoven/textures/item/black_diamond.png", + "environment": "*", + "entrypoints": { + "main": [ + "com.thebrokenrail.herobrine.HerobrineRewoven" + ], + "client": [ + "com.thebrokenrail.herobrine.client.HerobrineRewovenClient" + ] + }, + "mixins": [ + "herobrine-rewoven.mixins.json" + ], + "depends": { + "fabricloader": ">=0.7.4", + "fabric": "*", + "minecraft": "1.16.x" + } +} diff --git a/src/main/resources/herobrine-rewoven.mixins.json b/src/main/resources/herobrine-rewoven.mixins.json new file mode 100644 index 0000000..fff214f --- /dev/null +++ b/src/main/resources/herobrine-rewoven.mixins.json @@ -0,0 +1,14 @@ +{ + "required": true, + "package": "com.thebrokenrail.herobrine.mixin", + "compatibilityLevel": "JAVA_8", + "client": [ + ], + "mixins": [ + "MixinPlayerEntity", + "MixinServerWorld" + ], + "injectors": { + "defaultRequire": 1 + } +}