From 042192566710972acee81465df0c858653756890 Mon Sep 17 00:00:00 2001 From: 19MisterX98 <41451155+19MisterX98@users.noreply.github.com> Date: Mon, 7 Sep 2020 14:38:16 +0200 Subject: [PATCH] first commit --- .gitignore | 28 ++ LICENSE.md | 21 ++ README.md | 97 +++++++ build.gradle | 100 +++++++ gradle.properties | 17 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 55616 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 188 ++++++++++++++ gradlew.bat | 100 +++++++ settings.gradle | 10 + .../kaptainwutax/seedcracker/Features.java | 66 +++++ .../kaptainwutax/seedcracker/SeedCracker.java | 52 ++++ .../seedcracker/command/ClientCommand.java | 29 +++ .../seedcracker/command/CrackerCommand.java | 27 ++ .../seedcracker/command/DataCommand.java | 44 ++++ .../seedcracker/command/FinderCommand.java | 64 +++++ .../seedcracker/command/RenderCommand.java | 41 +++ .../seedcracker/command/VersionCommand.java | 34 +++ .../seedcracker/cracker/BiomeData.java | 34 +++ .../seedcracker/cracker/DataAddedEvent.java | 15 ++ .../seedcracker/cracker/HashedSeedData.java | 22 ++ .../seedcracker/cracker/PillarData.java | 39 +++ .../cracker/decorator/Decorator.java | 78 ++++++ .../cracker/decorator/Dungeon.java | 187 +++++++++++++ .../cracker/decorator/EmeraldOre.java | 83 ++++++ .../cracker/storage/DataStorage.java | 204 +++++++++++++++ .../cracker/storage/ProgressListener.java | 27 ++ .../cracker/storage/ScheduledSet.java | 48 ++++ .../cracker/storage/TimeMachine.java | 245 ++++++++++++++++++ .../seedcracker/finder/BiomeFinder.java | 69 +++++ .../seedcracker/finder/BlockFinder.java | 44 ++++ .../seedcracker/finder/Finder.java | 154 +++++++++++ .../seedcracker/finder/FinderBuilder.java | 13 + .../seedcracker/finder/FinderQueue.java | 83 ++++++ .../finder/decorator/DesertWellFinder.java | 104 ++++++++ .../finder/decorator/DungeonFinder.java | 144 ++++++++++ .../finder/decorator/EndGatewayFinder.java | 88 +++++++ .../finder/decorator/EndPillarsFinder.java | 97 +++++++ .../finder/decorator/OreFinder.java | 206 +++++++++++++++ .../decorator/ore/EmeraldOreFinder.java | 64 +++++ .../structure/AbstractTempleFinder.java | 82 ++++++ .../structure/BuriedTreasureFinder.java | 97 +++++++ .../finder/structure/DesertPyramidFinder.java | 232 +++++++++++++++++ .../finder/structure/EndCityFinder.java | 135 ++++++++++ .../finder/structure/IglooFinder.java | 101 ++++++++ .../finder/structure/JunglePyramidFinder.java | 138 ++++++++++ .../finder/structure/MansionFinder.java | 123 +++++++++ .../finder/structure/MonumentFinder.java | 139 ++++++++++ .../finder/structure/PieceFinder.java | 237 +++++++++++++++++ .../finder/structure/ShipwreckFinder.java | 217 ++++++++++++++++ .../finder/structure/SwampHutFinder.java | 98 +++++++ .../seedcracker/init/ClientCommands.java | 89 +++++++ .../mixin/ClientPlayNetworkHandlerMixin.java | 85 ++++++ .../mixin/ClientPlayerEntityMixin.java | 38 +++ .../seedcracker/mixin/ClientWorldMixin.java | 26 ++ .../seedcracker/mixin/DimensionTypeMixin.java | 20 ++ .../seedcracker/mixin/DummyProfilerMixin.java | 18 ++ .../seedcracker/mixin/GameRendererMixin.java | 24 ++ .../mixin/ServerChunkManagerMixin.java | 26 ++ .../seedcracker/profile/CustomProfile.java | 19 ++ .../seedcracker/profile/FinderConfig.java | 55 ++++ .../seedcracker/profile/FinderProfile.java | 34 +++ .../seedcracker/profile/NopeProfile.java | 11 + .../seedcracker/profile/VanillaProfile.java | 13 + .../seedcracker/profile/YoloProfile.java | 12 + .../seedcracker/render/Color.java | 41 +++ .../kaptainwutax/seedcracker/render/Cube.java | 25 ++ .../seedcracker/render/Cuboid.java | 62 +++++ .../kaptainwutax/seedcracker/render/Line.java | 71 +++++ .../seedcracker/render/RenderQueue.java | 52 ++++ .../seedcracker/render/Renderer.java | 19 ++ .../seedcracker/util/BiomeFixer.java | 18 ++ .../kaptainwutax/seedcracker/util/Log.java | 65 +++++ .../seedcracker/util/PosIterator.java | 24 ++ .../seedcracker/util/Predicates.java | 14 + .../resources/assets/seedcracker/icon.png | Bin 0 -> 453 bytes .../assets/seedcracker/lang/en_us.json | 4 + .../assets/seedcracker/lang/zh_cn.json | 4 + src/main/resources/fabric.mod.json | 35 +++ src/main/resources/seedcracker.mixins.json | 20 ++ taskBuild.bat | 1 + taskSetup.bat | 1 + 82 files changed, 5497 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle create mode 100644 src/main/java/kaptainwutax/seedcracker/Features.java create mode 100644 src/main/java/kaptainwutax/seedcracker/SeedCracker.java create mode 100644 src/main/java/kaptainwutax/seedcracker/command/ClientCommand.java create mode 100644 src/main/java/kaptainwutax/seedcracker/command/CrackerCommand.java create mode 100644 src/main/java/kaptainwutax/seedcracker/command/DataCommand.java create mode 100644 src/main/java/kaptainwutax/seedcracker/command/FinderCommand.java create mode 100644 src/main/java/kaptainwutax/seedcracker/command/RenderCommand.java create mode 100644 src/main/java/kaptainwutax/seedcracker/command/VersionCommand.java create mode 100644 src/main/java/kaptainwutax/seedcracker/cracker/BiomeData.java create mode 100644 src/main/java/kaptainwutax/seedcracker/cracker/DataAddedEvent.java create mode 100644 src/main/java/kaptainwutax/seedcracker/cracker/HashedSeedData.java create mode 100644 src/main/java/kaptainwutax/seedcracker/cracker/PillarData.java create mode 100644 src/main/java/kaptainwutax/seedcracker/cracker/decorator/Decorator.java create mode 100644 src/main/java/kaptainwutax/seedcracker/cracker/decorator/Dungeon.java create mode 100644 src/main/java/kaptainwutax/seedcracker/cracker/decorator/EmeraldOre.java create mode 100644 src/main/java/kaptainwutax/seedcracker/cracker/storage/DataStorage.java create mode 100644 src/main/java/kaptainwutax/seedcracker/cracker/storage/ProgressListener.java create mode 100644 src/main/java/kaptainwutax/seedcracker/cracker/storage/ScheduledSet.java create mode 100644 src/main/java/kaptainwutax/seedcracker/cracker/storage/TimeMachine.java create mode 100644 src/main/java/kaptainwutax/seedcracker/finder/BiomeFinder.java create mode 100644 src/main/java/kaptainwutax/seedcracker/finder/BlockFinder.java create mode 100644 src/main/java/kaptainwutax/seedcracker/finder/Finder.java create mode 100644 src/main/java/kaptainwutax/seedcracker/finder/FinderBuilder.java create mode 100644 src/main/java/kaptainwutax/seedcracker/finder/FinderQueue.java create mode 100644 src/main/java/kaptainwutax/seedcracker/finder/decorator/DesertWellFinder.java create mode 100644 src/main/java/kaptainwutax/seedcracker/finder/decorator/DungeonFinder.java create mode 100644 src/main/java/kaptainwutax/seedcracker/finder/decorator/EndGatewayFinder.java create mode 100644 src/main/java/kaptainwutax/seedcracker/finder/decorator/EndPillarsFinder.java create mode 100644 src/main/java/kaptainwutax/seedcracker/finder/decorator/OreFinder.java create mode 100644 src/main/java/kaptainwutax/seedcracker/finder/decorator/ore/EmeraldOreFinder.java create mode 100644 src/main/java/kaptainwutax/seedcracker/finder/structure/AbstractTempleFinder.java create mode 100644 src/main/java/kaptainwutax/seedcracker/finder/structure/BuriedTreasureFinder.java create mode 100644 src/main/java/kaptainwutax/seedcracker/finder/structure/DesertPyramidFinder.java create mode 100644 src/main/java/kaptainwutax/seedcracker/finder/structure/EndCityFinder.java create mode 100644 src/main/java/kaptainwutax/seedcracker/finder/structure/IglooFinder.java create mode 100644 src/main/java/kaptainwutax/seedcracker/finder/structure/JunglePyramidFinder.java create mode 100644 src/main/java/kaptainwutax/seedcracker/finder/structure/MansionFinder.java create mode 100644 src/main/java/kaptainwutax/seedcracker/finder/structure/MonumentFinder.java create mode 100644 src/main/java/kaptainwutax/seedcracker/finder/structure/PieceFinder.java create mode 100644 src/main/java/kaptainwutax/seedcracker/finder/structure/ShipwreckFinder.java create mode 100644 src/main/java/kaptainwutax/seedcracker/finder/structure/SwampHutFinder.java create mode 100644 src/main/java/kaptainwutax/seedcracker/init/ClientCommands.java create mode 100644 src/main/java/kaptainwutax/seedcracker/mixin/ClientPlayNetworkHandlerMixin.java create mode 100644 src/main/java/kaptainwutax/seedcracker/mixin/ClientPlayerEntityMixin.java create mode 100644 src/main/java/kaptainwutax/seedcracker/mixin/ClientWorldMixin.java create mode 100644 src/main/java/kaptainwutax/seedcracker/mixin/DimensionTypeMixin.java create mode 100644 src/main/java/kaptainwutax/seedcracker/mixin/DummyProfilerMixin.java create mode 100644 src/main/java/kaptainwutax/seedcracker/mixin/GameRendererMixin.java create mode 100644 src/main/java/kaptainwutax/seedcracker/mixin/ServerChunkManagerMixin.java create mode 100644 src/main/java/kaptainwutax/seedcracker/profile/CustomProfile.java create mode 100644 src/main/java/kaptainwutax/seedcracker/profile/FinderConfig.java create mode 100644 src/main/java/kaptainwutax/seedcracker/profile/FinderProfile.java create mode 100644 src/main/java/kaptainwutax/seedcracker/profile/NopeProfile.java create mode 100644 src/main/java/kaptainwutax/seedcracker/profile/VanillaProfile.java create mode 100644 src/main/java/kaptainwutax/seedcracker/profile/YoloProfile.java create mode 100644 src/main/java/kaptainwutax/seedcracker/render/Color.java create mode 100644 src/main/java/kaptainwutax/seedcracker/render/Cube.java create mode 100644 src/main/java/kaptainwutax/seedcracker/render/Cuboid.java create mode 100644 src/main/java/kaptainwutax/seedcracker/render/Line.java create mode 100644 src/main/java/kaptainwutax/seedcracker/render/RenderQueue.java create mode 100644 src/main/java/kaptainwutax/seedcracker/render/Renderer.java create mode 100644 src/main/java/kaptainwutax/seedcracker/util/BiomeFixer.java create mode 100644 src/main/java/kaptainwutax/seedcracker/util/Log.java create mode 100644 src/main/java/kaptainwutax/seedcracker/util/PosIterator.java create mode 100644 src/main/java/kaptainwutax/seedcracker/util/Predicates.java create mode 100644 src/main/resources/assets/seedcracker/icon.png create mode 100644 src/main/resources/assets/seedcracker/lang/en_us.json create mode 100644 src/main/resources/assets/seedcracker/lang/zh_cn.json create mode 100644 src/main/resources/fabric.mod.json create mode 100644 src/main/resources/seedcracker.mixins.json create mode 100644 taskBuild.bat create mode 100644 taskSetup.bat diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..1428f8d5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +# gradle + +.gradle/ +build/ +out/ +classes/ + +# idea + +.idea/ +*.iml +*.ipr +*.iws + +# vscode + +.settings/ +.vscode/ +bin/ +.classpath +.project + +# fabric + +run/ +logs/ + +src/main/java/kaptainwutax/stronghold/ diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..9bc0518e --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 KaptainWutax + +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. diff --git a/README.md b/README.md new file mode 100644 index 00000000..825d8f8e --- /dev/null +++ b/README.md @@ -0,0 +1,97 @@ +# SeedCracker [![Github All Releases](https://img.shields.io/github/downloads/KaptainWutax/SeedCracker/total.svg)]() + +## Installation + + ### Vanilla Launcher + + Download and install the [fabric mod loader](https://fabricmc.net/use/). + + ### MultiMC + + Add a new minecraft instance and press "Install Fabric" in the instance options. + + + Then download the lastest [release](https://github.com/KaptainWutax/SeedCracker/releases) of SeedCracker and put the `.jar` file in your mods directory, either `%appdata%/.minecraft/mods/` folder for the vanilla launcher or your own MultiMC instance folder. + +## Usage + + Run minecraft with the mod installed and run around in the world. Once the mod has collected enough data, it will start the cracking process automatically and output the seed in chat. For the process to start, the amount of data that needs to be collected varies depending on the type of feature. `/seed data bits` can be used to see how much progress has been done. + + ### Supported Structures + - Ocean Monument + - End City + - Buried Treasure + - Desert Pyramid + - Jungle Temple + - Swamp Hut + - Shipwreck + + ### Supported Decorators + - Dungeon + - End Gateway + - Desert Well + - Emerald Ore + +## Commands + + The command prefix for this mod is /seed. + + ### Render Command + -`/seed render outlines ` + + This command only affects the renderer feedback. The default value is 'XRAY' and highlights data through blocks. You can set the render mod to 'ON' for more standard rendering. + + ### Finder Command + -`/seed finder type (ON/OFF)` + + -`/seed finder category (BIOMES/ORES/OTHERS/STRUCTURES) (ON/OFF)` + + This command is used to disable finders in case you are aware the data is wrong. For example, a map generated in 1.14 has different decorators and would require you to disable them while going through those chunks. + + ### Data Command + - `/seed data clear` + + Clears all the collected data without requiring a relog. This is useful for multi-world servers. + + - `/seed data bits` + + Display how many bits of information have been collected. Even though this is an approximate, it serves as a good basis to guess when the brute-forcing should start. + + ### Cracker Command + - `/seed cracker ` + + Enables or disables the mod completely. Unlike the other commands, this one is persistent across reloads. + +## Video Tutorial + +https://youtu.be/1ChmLi9og8Q + +## Upcoming Features + +A list of features I have on my mind... they won't necessarily be implemented in this order if at all. + + - SHA2 brute-forcing, auxiliary to biomes search. /implemented + - Dungeon floor cracker, fast lattice reversal. /implemented + - Stronghold portal room cracker. (alternative to dungeon floor?) + - Faster brute-forcing by reorganizing located features list. /implemented + - End and nether biome finders. (nether would mostly be in preparation for 1.16) /implemented + +## Setting up the Workspace + +-Clone the repository. + +-Run `gradlew genSources `. + +## Building the Mod + +-Update the version in `build.gradle` and `fabric.mod.json`. + +-Run `gradlew build`. + +## Contributors + +[KaptainWutax](https://github.com/KaptainWutax) - Author + +[neil](https://www.youtube.com/watch?v=aUuPSZVPH8E) - Video Tutorial + +[Nekzuris](https://github.com/Nekzuris) - README diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..5fd347de --- /dev/null +++ b/build.gradle @@ -0,0 +1,100 @@ +plugins { + id 'fabric-loom' version '0.4-SNAPSHOT' + id 'maven-publish' +} + +sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_8 + +archivesBaseName = project.archives_base_name +version = project.mod_version +group = project.maven_group + +minecraft { +} + +repositories { + maven { + url "https://jitpack.io" + } +} + +dependencies { + //to change the versions see the gradle.properties file + minecraft "com.mojang:minecraft:${project.minecraft_version}" + mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" + modCompile "net.fabricmc:fabric-loader:${project.loader_version}" + + implementation('com.github.KaptainWutax:MathUtils:6c2d50eacad0241ff76119e6e703b70bac4b4bce') {transitive = false} + implementation('com.github.KaptainWutax:SeedUtils:0de70bc772fef95d8acfa6991e7278ee53a8b46c') {transitive = false} + implementation('com.github.KaptainWutax:FeatureUtils:25f73f26289a65a314cd66badc3c433d7f8c37b0') {transitive = false} + implementation('com.github.KaptainWutax:BiomeUtils:590f697a2ccb6c6bdba8e2fea891a25ace75c947') {transitive = false} + implementation('com.github.KaptainWutax:ChunkRandomReversal:209eefb8ed2bd097e3c55d3934ba508b664443da') {transitive = false} + implementation('com.github.KaptainWutax:LattiCG:38f0b3d33e15ad2e6ce9ddb1f588e2b9a8c96174') {transitive = false} + + include('com.github.KaptainWutax:MathUtils:6c2d50eacad0241ff76119e6e703b70bac4b4bce') {transitive = false} + include('com.github.KaptainWutax:SeedUtils:0de70bc772fef95d8acfa6991e7278ee53a8b46c') {transitive = false} + include('com.github.KaptainWutax:FeatureUtils:25f73f26289a65a314cd66badc3c433d7f8c37b0') {transitive = false} + include('com.github.KaptainWutax:BiomeUtils:590f697a2ccb6c6bdba8e2fea891a25ace75c947') {transitive = false} + include('com.github.KaptainWutax:ChunkRandomReversal:209eefb8ed2bd097e3c55d3934ba508b664443da') {transitive = false} + include('com.github.KaptainWutax:LattiCG:38f0b3d33e15ad2e6ce9ddb1f588e2b9a8c96174') {transitive = false} + + // Fabric API. This is technically optional, but you probably want it anyway. + // modCompile "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" + + // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. + // You may need to force-disable transitiveness on them. +} + +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 +} + +jar { + from "LICENSE" +} + +// configure the maven publication +publishing { + publications { + mavenJava(MavenPublication) { + // add all the jars that should be included when publishing to maven + artifact(remapJar) { + builtBy remapJar + } + artifact(sourcesJar) { + builtBy remapSourcesJar + } + } + } + + // select the repositories you want to publish to + repositories { + // uncomment to publish to the local maven + // mavenLocal() + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000..bf37f58b --- /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.2 + yarn_mappings=1.16.2+build.6 + loader_version=0.9.1+build.205 + +# Mod Properties + mod_version = 0.2.2-beta + maven_group = kaptainwutax + archives_base_name = seedcracker + +# Dependencies + # currently not on the main fabric site, check on the maven: https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api + fabric_version=0.14.0+build.371-1.16 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..5c2d1cf016b3885f6930543d57b744ea8c220a1a GIT binary patch literal 55616 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 \(.*\)$'` + 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 00000000..9618d8d9 --- /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 00000000..5b60df3d --- /dev/null +++ b/settings.gradle @@ -0,0 +1,10 @@ +pluginManagement { + repositories { + jcenter() + maven { + name = 'Fabric' + url = 'https://maven.fabricmc.net/' + } + gradlePluginPortal() + } +} diff --git a/src/main/java/kaptainwutax/seedcracker/Features.java b/src/main/java/kaptainwutax/seedcracker/Features.java new file mode 100644 index 00000000..7036be77 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/Features.java @@ -0,0 +1,66 @@ +package kaptainwutax.seedcracker; + +import kaptainwutax.featureutils.decorator.DesertWell; +import kaptainwutax.featureutils.decorator.EndGateway; +import kaptainwutax.featureutils.structure.*; +import kaptainwutax.seedcracker.cracker.decorator.Dungeon; +import kaptainwutax.seedcracker.cracker.decorator.EmeraldOre; +import kaptainwutax.seedutils.mc.MCVersion; + +public class Features { + + public static BastionRemnant BASTION_REMNANT; + public static BuriedTreasure BURIED_TREASURE; + public static DesertPyramid DESERT_PYRAMID; + public static EndCity END_CITY; + public static Fortress FORTRESS; + public static Igloo IGLOO; + public static JunglePyramid JUNGLE_PYRAMID; + public static Mansion MANSION; + public static Mineshaft MINESHAFT; + public static Monument MONUMENT; + public static NetherFossil NETHER_FOSSIL; + public static OceanRuin OCEAN_RUIN; + public static PillagerOutpost PILLAGER_OUTPOST; + public static RuinedPortal RUINED_PORTAL; + public static Shipwreck SHIPWRECK; + public static Stronghold STRONGHOLD; + public static SwampHut SWAMP_HUT; + public static Village VILLAGE; + + public static EndGateway END_GATEWAY; + public static DesertWell DESERT_WELL; + public static EmeraldOre EMERALD_ORE; + public static Dungeon DUNGEON; + + public static void init(MCVersion version) { + safe(() -> BASTION_REMNANT = new BastionRemnant(version)); + safe(() -> BURIED_TREASURE = new BuriedTreasure(version)); + safe(() -> DESERT_PYRAMID = new DesertPyramid(version)); + safe(() -> END_CITY = new EndCity(version)); + safe(() -> FORTRESS = new Fortress(version)); + safe(() -> IGLOO = new Igloo(version)); + safe(() -> JUNGLE_PYRAMID = new JunglePyramid(version)); + safe(() -> MANSION = new Mansion(version)); + safe(() -> MINESHAFT = new Mineshaft(version)); + safe(() -> MONUMENT = new Monument(version)); + safe(() -> NETHER_FOSSIL = new NetherFossil(version)); + safe(() -> OCEAN_RUIN = new OceanRuin(version)); + safe(() -> PILLAGER_OUTPOST = new PillagerOutpost(version)); + safe(() -> RUINED_PORTAL = new RuinedPortal(version)); + safe(() -> SHIPWRECK = new Shipwreck(version)); + safe(() -> STRONGHOLD = new Stronghold(version)); + safe(() -> SWAMP_HUT = new SwampHut(version)); + safe(() -> VILLAGE = new Village(version)); + + safe(() -> END_GATEWAY = new EndGateway(version)); + safe(() -> DESERT_WELL = new DesertWell(version)); + safe(() -> EMERALD_ORE = new EmeraldOre(version)); + safe(() -> DUNGEON = new Dungeon(version)); + } + + private static void safe(Runnable runnable) { + try {runnable.run();} catch(Exception e) {} + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/SeedCracker.java b/src/main/java/kaptainwutax/seedcracker/SeedCracker.java new file mode 100644 index 00000000..532969d3 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/SeedCracker.java @@ -0,0 +1,52 @@ +package kaptainwutax.seedcracker; + +import kaptainwutax.seedcracker.command.ClientCommand; +import kaptainwutax.seedcracker.cracker.storage.DataStorage; +import kaptainwutax.seedcracker.finder.FinderQueue; +import kaptainwutax.seedcracker.render.RenderQueue; +import kaptainwutax.seedutils.mc.MCVersion; +import net.fabricmc.api.ModInitializer; +import net.minecraft.util.Formatting; + +public class SeedCracker implements ModInitializer { + + public static MCVersion MC_VERSION = MCVersion.v1_16_2; + + private static final SeedCracker INSTANCE = new SeedCracker(); + private final DataStorage dataStorage = new DataStorage(); + private boolean active = true; + + @Override + public void onInitialize() { + Features.init(MC_VERSION); + RenderQueue.get().add("hand", FinderQueue.get()::renderFinders); + } + + public static SeedCracker get() { + return INSTANCE; + } + + public DataStorage getDataStorage() { + return this.dataStorage; + } + + public boolean isActive() { + return this.active; + } + + public void setActive(boolean active) { + this.active = active; + + if(this.active) { + ClientCommand.sendFeedback("SeedCracker is active.", Formatting.GREEN, true); + } else { + ClientCommand.sendFeedback("SeedCracker is not active.", Formatting.RED, true); + } + } + + public void reset() { + SeedCracker.get().getDataStorage().clear(); + FinderQueue.get().clear(); + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/command/ClientCommand.java b/src/main/java/kaptainwutax/seedcracker/command/ClientCommand.java new file mode 100644 index 00000000..0aca9576 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/command/ClientCommand.java @@ -0,0 +1,29 @@ +package kaptainwutax.seedcracker.command; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import kaptainwutax.seedcracker.init.ClientCommands; +import net.minecraft.client.MinecraftClient; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.text.LiteralText; +import net.minecraft.util.Formatting; + +import static net.minecraft.server.command.CommandManager.literal; + +public abstract class ClientCommand { + + public abstract String getName(); + + public abstract void build(LiteralArgumentBuilder builder); + + public static void sendFeedback(String message, Formatting color, boolean overlay) { + MinecraftClient.getInstance().player.sendMessage(new LiteralText(message).formatted(color), overlay); + } + + public final void register(CommandDispatcher dispatcher) { + LiteralArgumentBuilder builder = literal(this.getName()); + this.build(builder); + dispatcher.register(literal(ClientCommands.PREFIX).then(builder)); + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/command/CrackerCommand.java b/src/main/java/kaptainwutax/seedcracker/command/CrackerCommand.java new file mode 100644 index 00000000..54036e5f --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/command/CrackerCommand.java @@ -0,0 +1,27 @@ +package kaptainwutax.seedcracker.command; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import kaptainwutax.seedcracker.SeedCracker; +import net.minecraft.server.command.ServerCommandSource; + +import static net.minecraft.server.command.CommandManager.literal; + +public class CrackerCommand extends ClientCommand { + + @Override + public String getName() { + return "cracker"; + } + + @Override + public void build(LiteralArgumentBuilder builder) { + builder.then(literal("ON").executes(context -> this.setActive(true))) + .then(literal("OFF").executes(context -> this.setActive(false))); + } + + private int setActive(boolean flag) { + SeedCracker.get().setActive(flag); + return 0; + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/command/DataCommand.java b/src/main/java/kaptainwutax/seedcracker/command/DataCommand.java new file mode 100644 index 00000000..810b2390 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/command/DataCommand.java @@ -0,0 +1,44 @@ +package kaptainwutax.seedcracker.command; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import kaptainwutax.seedcracker.SeedCracker; +import kaptainwutax.seedcracker.cracker.storage.DataStorage; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.util.Formatting; + +import static net.minecraft.server.command.CommandManager.literal; + +public class DataCommand extends ClientCommand { + + @Override + public String getName() { + return "data"; + } + + @Override + public void build(LiteralArgumentBuilder builder) { + builder.then(literal("clear") + .executes(this::clear) + ); + + builder.then(literal("bits") + .executes(this::printBits) + ); + } + + public int clear(CommandContext context) { + SeedCracker.get().reset(); + sendFeedback("Cleared data storage and finders.", Formatting.GREEN, false); + return 0; + } + + private int printBits(CommandContext context) { + DataStorage s = SeedCracker.get().getDataStorage(); + String message = "You currently have collected " + (int)s.getBaseBits() + " bits out of " + (int)s.getWantedBits() + "."; + sendFeedback(message, Formatting.GREEN, false); + return 0; + } + +} + diff --git a/src/main/java/kaptainwutax/seedcracker/command/FinderCommand.java b/src/main/java/kaptainwutax/seedcracker/command/FinderCommand.java new file mode 100644 index 00000000..0b9bd92c --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/command/FinderCommand.java @@ -0,0 +1,64 @@ +package kaptainwutax.seedcracker.command; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import kaptainwutax.seedcracker.finder.Finder; +import kaptainwutax.seedcracker.finder.FinderQueue; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.util.Formatting; + +import static net.minecraft.server.command.CommandManager.literal; + +public class FinderCommand extends ClientCommand { + + @Override + public String getName() { + return "finder"; + } + + @Override + public void build(LiteralArgumentBuilder builder) { + for(Finder.Type finderType: Finder.Type.values()) { + builder.then(literal("type") + .then(literal(finderType.toString()) + .then(literal("ON").executes(context -> this.setFinderType(finderType, true))) + .then(literal("OFF").executes(context -> this.setFinderType(finderType, false)))) + .executes(context -> this.printFinderType(finderType)) + ); + } + + for(Finder.Category finderCategory: Finder.Category.values()) { + builder.then(literal("category") + .then(literal(finderCategory.toString()) + .then(literal("ON").executes(context -> this.setFinderCategory(finderCategory, true))) + .then(literal("OFF").executes(context -> this.setFinderCategory(finderCategory, false)))) + .executes(context -> this.printFinderCategory(finderCategory)) + ); + } + } + + private int printFinderCategory(Finder.Category finderCategory) { + Finder.Type.getForCategory(finderCategory).forEach(this::printFinderType); + return 0; + } + + private int printFinderType(Finder.Type finderType) { + sendFeedback("Finder " + finderType + " is set to [" + String.valueOf(FinderQueue.get().finderProfile.getActive(finderType)).toUpperCase() + "].", Formatting.AQUA,false); + return 0; + } + + private int setFinderCategory(Finder.Category finderCategory, boolean flag) { + Finder.Type.getForCategory(finderCategory).forEach(finderType -> this.setFinderType(finderType, flag)); + return 0; + } + + private int setFinderType(Finder.Type finderType, boolean flag) { + if(FinderQueue.get().finderProfile.setActive(finderType, flag)) { + sendFeedback("Finder " + finderType + " has been set to [" + String.valueOf(flag).toUpperCase() + "].", Formatting.AQUA, false); + } else { + sendFeedback("Your current finder profile is locked and cannot be modified. Please make a copy first.", Formatting.RED, false); + } + + return 0; + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/command/RenderCommand.java b/src/main/java/kaptainwutax/seedcracker/command/RenderCommand.java new file mode 100644 index 00000000..31f0c68e --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/command/RenderCommand.java @@ -0,0 +1,41 @@ +package kaptainwutax.seedcracker.command; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import kaptainwutax.seedcracker.finder.FinderQueue; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.util.Formatting; + +import static net.minecraft.server.command.CommandManager.literal; + +public class RenderCommand extends ClientCommand { + + @Override + public String getName() { + return "render"; + } + + @Override + public void build(LiteralArgumentBuilder builder) { + builder.then(literal("outlines") + .executes(context -> this.printRenderMode()) + ); + + for(FinderQueue.RenderType renderType: FinderQueue.RenderType.values()) { + builder.then(literal("outlines") + .then(literal(renderType.toString()).executes(context -> this.setRenderMode(renderType))) + ); + } + } + + private int printRenderMode() { + sendFeedback("Current render mode is set to [" + FinderQueue.get().renderType + "].", Formatting.AQUA, false); + return 0; + } + + private int setRenderMode(FinderQueue.RenderType renderType) { + FinderQueue.get().renderType = renderType; + sendFeedback("Set render mode to [" + FinderQueue.get().renderType + "].", Formatting.AQUA, false); + return 0; + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/command/VersionCommand.java b/src/main/java/kaptainwutax/seedcracker/command/VersionCommand.java new file mode 100644 index 00000000..3fb271f5 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/command/VersionCommand.java @@ -0,0 +1,34 @@ +package kaptainwutax.seedcracker.command; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import kaptainwutax.seedcracker.Features; +import kaptainwutax.seedcracker.SeedCracker; +import kaptainwutax.seedutils.mc.MCVersion; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.util.Formatting; + +import static net.minecraft.server.command.CommandManager.literal; + +public class VersionCommand extends ClientCommand { + + @Override + public String getName() { + return "version"; + } + + @Override + public void build(LiteralArgumentBuilder builder) { + for(MCVersion version: MCVersion.values()) { + if(version.isOlderThan(MCVersion.v1_13))continue; + builder.then(literal(version.name).executes(context -> this.setVersion(version))); + } + } + + private int setVersion(MCVersion version) { + SeedCracker.MC_VERSION = version; + Features.init(SeedCracker.MC_VERSION); + ClientCommand.sendFeedback("Changed version to " + version + ".", Formatting.AQUA, true); + return 0; + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/cracker/BiomeData.java b/src/main/java/kaptainwutax/seedcracker/cracker/BiomeData.java new file mode 100644 index 00000000..df3e7432 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/cracker/BiomeData.java @@ -0,0 +1,34 @@ +package kaptainwutax.seedcracker.cracker; + +import kaptainwutax.biomeutils.Biome; +import kaptainwutax.biomeutils.source.BiomeSource; + +public class BiomeData { + + public final Biome biome; + public final int x; + public final int z; + + public BiomeData(Biome biome, int x, int z) { + this.biome = biome; + this.x = x; + this.z = z; + } + + public boolean test(BiomeSource source) { + return source.getBiomeForNoiseGen(this.x, 0, this.z) == this.biome; + } + + @Override + public boolean equals(Object o) { + if(this == o) return true; + if(!(o instanceof BiomeData))return false; + BiomeData data = (BiomeData)o; + return this.biome == data.biome; + } + + @Override + public int hashCode() { + return this.biome.getName().hashCode(); + } +} diff --git a/src/main/java/kaptainwutax/seedcracker/cracker/DataAddedEvent.java b/src/main/java/kaptainwutax/seedcracker/cracker/DataAddedEvent.java new file mode 100644 index 00000000..615e5ff5 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/cracker/DataAddedEvent.java @@ -0,0 +1,15 @@ +package kaptainwutax.seedcracker.cracker; + +import kaptainwutax.seedcracker.cracker.storage.DataStorage; +import kaptainwutax.seedcracker.cracker.storage.TimeMachine; + +@FunctionalInterface +public interface DataAddedEvent { + + DataAddedEvent POKE_PILLARS = s -> s.getTimeMachine().poke(TimeMachine.Phase.PILLARS); + DataAddedEvent POKE_STRUCTURES = s -> s.getTimeMachine().poke(TimeMachine.Phase.STRUCTURES); + DataAddedEvent POKE_BIOMES = s -> s.getTimeMachine().poke(TimeMachine.Phase.BIOMES); + + void onDataAdded(DataStorage dataStorage); + +} diff --git a/src/main/java/kaptainwutax/seedcracker/cracker/HashedSeedData.java b/src/main/java/kaptainwutax/seedcracker/cracker/HashedSeedData.java new file mode 100644 index 00000000..e499e79e --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/cracker/HashedSeedData.java @@ -0,0 +1,22 @@ +package kaptainwutax.seedcracker.cracker; + +import kaptainwutax.seedutils.lcg.rand.JRand; +import kaptainwutax.seedutils.mc.seed.WorldSeed; + +public class HashedSeedData { + + private final long hashedSeed; + + public HashedSeedData(long hashedSeed) { + this.hashedSeed = hashedSeed; + } + + public boolean test(long seed, JRand rand) { + return WorldSeed.toHash(seed) == this.hashedSeed; + } + + public long getHashedSeed() { + return this.hashedSeed; + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/cracker/PillarData.java b/src/main/java/kaptainwutax/seedcracker/cracker/PillarData.java new file mode 100644 index 00000000..af79d235 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/cracker/PillarData.java @@ -0,0 +1,39 @@ +package kaptainwutax.seedcracker.cracker; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +public class PillarData { + + private List heights; + + public PillarData(List heights) { + this.heights = heights; + } + + public boolean test(long seed) { + List h = this.getPillarHeights((int)seed); + return h.equals(this.heights); + } + + public List getPillarHeights(int pillarSeed) { + List indices = new ArrayList<>(); + + for(int i = 0; i < 10; i++) { + indices.add(i); + } + + Collections.shuffle(indices, new Random(pillarSeed)); + + List heights = new ArrayList<>(); + + for(Integer index : indices) { + heights.add(76 + index * 3); + } + + return heights; + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/cracker/decorator/Decorator.java b/src/main/java/kaptainwutax/seedcracker/cracker/decorator/Decorator.java new file mode 100644 index 00000000..37f6d4c4 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/cracker/decorator/Decorator.java @@ -0,0 +1,78 @@ +package kaptainwutax.seedcracker.cracker.decorator; + +import kaptainwutax.biomeutils.Biome; +import kaptainwutax.biomeutils.source.BiomeSource; +import kaptainwutax.featureutils.Feature; +import kaptainwutax.seedutils.mc.ChunkRand; +import kaptainwutax.seedutils.mc.MCVersion; + +import java.util.HashMap; +import java.util.Map; + +public abstract class Decorator> extends Feature { + + public Decorator(C config, MCVersion version) { + super(config, version); + } + + public int getIndex(Biome biome) { + return this.getConfig().getSalt(biome) % 10000; + } + + public int getStep(Biome biome) { + return this.getConfig().getSalt(biome) / 10000; + } + + @Override + public boolean canStart(D data, long structureSeed, ChunkRand rand) { + rand.setDecoratorSeed(structureSeed, data.chunkX << 4, data.chunkZ << 4, + this.getIndex(data.biome), this.getStep(data.biome), this.getVersion()); + return true; + } + + @Override + public final boolean canSpawn(D data, BiomeSource source) { + return this.canSpawn(data.chunkX, data.chunkZ, source); + } + + public boolean canSpawn(int chunkX, int chunkZ, BiomeSource source) { + if(this.getVersion().isOlderThan(MCVersion.v1_16)) { + return this.isValidBiome(source.getBiome((chunkX << 4) + 8, 0, (chunkZ << 4) + 8)); + } + + return this.isValidBiome(source.getBiomeForNoiseGen((chunkX << 2) + 2, 0, (chunkZ << 2) + 2)); + } + + public abstract boolean isValidBiome(Biome biome); + + public static class Config extends Feature.Config { + public final int defaultSalt; + public final Map salts = new HashMap<>(); + + public Config(int step, int index) { + this.defaultSalt = step * 10000 + index; + } + + public Config add(int step, int index, Biome... biomes) { + for(Biome biome: biomes) { + this.salts.put(biome, step * 10000 + index); + } + + return this; + } + + public int getSalt(Biome biome) { + return this.salts.getOrDefault(biome, this.defaultSalt); + } + } + + public static class Data> extends Feature.Data { + public final Biome biome; + + public Data(T feature, int chunkX, int chunkZ, Biome biome) { + super(feature, chunkX, chunkZ); + this.biome = biome; + } + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/cracker/decorator/Dungeon.java b/src/main/java/kaptainwutax/seedcracker/cracker/decorator/Dungeon.java new file mode 100644 index 00000000..39d38d54 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/cracker/decorator/Dungeon.java @@ -0,0 +1,187 @@ +package kaptainwutax.seedcracker.cracker.decorator; + +import kaptainwutax.biomeutils.Biome; +import kaptainwutax.seedcracker.SeedCracker; +import kaptainwutax.seedcracker.cracker.storage.DataStorage; +import kaptainwutax.seedcracker.cracker.storage.TimeMachine; +import kaptainwutax.seedcracker.util.Log; +import kaptainwutax.seedutils.lcg.LCG; +import kaptainwutax.seedutils.mc.ChunkRand; +import kaptainwutax.seedutils.mc.Dimension; +import kaptainwutax.seedutils.mc.MCVersion; +import kaptainwutax.seedutils.mc.VersionMap; +import mjtb49.hashreversals.ChunkRandomReverser; +import net.minecraft.util.math.Vec3i; +import randomreverser.call.java.FilteredSkip; +import randomreverser.call.java.NextInt; +import randomreverser.device.JavaRandomDevice; +import randomreverser.device.LCGReverserDevice; + +import java.util.ArrayList; +import java.util.Set; +import java.util.stream.Collectors; + +public class Dungeon extends Decorator { + + public static final VersionMap CONFIGS = new VersionMap() + .add(MCVersion.v1_13, new Decorator.Config(2, 3)) + .add(MCVersion.v1_16, new Decorator.Config(3, 2) + .add(3, 3, Biome.DESERT, Biome.SWAMP, Biome.SWAMP_HILLS)); + + public Dungeon(MCVersion version) { + super(CONFIGS.getAsOf(version), version); + } + + public Dungeon(Decorator.Config config) { + super(config, null); + } + + @Override + public String getName() { + return "dungeon"; + } + + @Override + public boolean canStart(Dungeon.Data data, long structureSeed, ChunkRand rand) { + super.canStart(data, structureSeed, rand); + + for(int i = 0; i < 8; i++) { + int x, y, z; + + if(this.getVersion().isOlderThan(MCVersion.v1_15)) { + x = rand.nextInt(16); + y = rand.nextInt(256); + z = rand.nextInt(16); + } else { + x = rand.nextInt(16); + z = rand.nextInt(16); + y = rand.nextInt(256); + } + + if(y == data.blockY && x == data.offsetX && z == data.offsetZ) { + return true; + } + + rand.nextInt(2); + rand.nextInt(2); + } + + return false; + } + + @Override + public boolean isValidDimension(Dimension dimension) { + return dimension == Dimension.OVERWORLD; + } + + @Override + public boolean isValidBiome(Biome biome) { + return biome != Biome.NETHER_WASTES && biome != Biome.SOUL_SAND_VALLEY && biome != Biome.WARPED_FOREST + && biome != Biome.CRIMSON_FOREST && biome != Biome.BASALT_DELTAS && biome != Biome.END_MIDLANDS + && biome != Biome.END_HIGHLANDS && biome != Biome.END_BARRENS && biome != Biome.SMALL_END_ISLANDS + && biome != Biome.THE_VOID && biome == Biome.THE_END; + } + + public Dungeon.Data at(int blockX, int blockY, int blockZ, Vec3i size, int[] floorCalls, Biome biome) { + return new Dungeon.Data(this, blockX, blockY, blockZ, size, floorCalls, biome); + } + + public static class Data extends Decorator.Data { + public static final int COBBLESTONE_CALL = 0; + public static final int MOSSY_COBBLESTONE_CALL = 1; + public static final float MIN_FLOOR_BITS = 26.0F; + public static final float MAX_FLOOR_BITS = 48.0F; + + public final int offsetX; + public final int blockX; + private final int blockY; + public final int offsetZ; + public final int blockZ; + public final Vec3i size; + public final int[] floorCalls; + public float bitsCount; + + public Data(Dungeon feature, int blockX, int blockY, int blockZ, Vec3i size, int[] floorCalls, Biome biome) { + super(feature, blockX >> 4, blockZ >> 4, biome); + this.offsetX = blockX & 15; + this.blockY = blockY; + this.offsetZ = blockZ & 15; + this.size = size; + this.floorCalls = floorCalls; + this.blockX = blockX; + this.blockZ = blockZ; + + if(floorCalls != null) { + for(int call: floorCalls) { + this.bitsCount += call == COBBLESTONE_CALL ? 2.0F : 0.0F; + } + } + } + + public boolean usesFloor() { + return this.bitsCount >= MIN_FLOOR_BITS && this.bitsCount <= MAX_FLOOR_BITS; + } + + public void onDataAdded(DataStorage dataStorage) { + dataStorage.getTimeMachine().poke(TimeMachine.Phase.STRUCTURES); + if(this.floorCalls == null || !this.usesFloor())return; + if(dataStorage.getTimeMachine().structureSeeds != null)return; + + Log.warn("Short-cutting to dungeons..."); + String boden = ""; + for(int block : floorCalls) { + boden = boden.concat(Integer.toString(block)); + } + Log.printDungeonInfo(", " + blockX + ", " + blockY + ", " + blockZ+", \"" + boden + "\""); + + JavaRandomDevice device = new JavaRandomDevice(); + + if(this.feature.getVersion().isOlderThan(MCVersion.v1_15)) { + device.addCall(NextInt.withValue(16, this.offsetX)); + device.addCall(NextInt.withValue(256, this.blockY)); + device.addCall(NextInt.withValue(16, this.offsetZ)); + } else { + device.addCall(NextInt.withValue(16, this.offsetX)); + device.addCall(NextInt.withValue(16, this.offsetZ)); + device.addCall(NextInt.withValue(256, this.blockY)); + } + + device.addCall(NextInt.consume(2, 2)); //Skip size. + + for(int call: this.floorCalls) { + if(call == COBBLESTONE_CALL) { + device.addCall(NextInt.withValue(4, 0)); + } else if(call == MOSSY_COBBLESTONE_CALL) { + //Skip mossy, brute-force later. + device.addCall(FilteredSkip.filter(LCG.JAVA, r -> r.nextInt(4) != 0, 1)); + } + } + + Set decoratorSeeds = device.streamSeeds(LCGReverserDevice.Process.EVERYTHING).sequential().limit(1).collect(Collectors.toSet()); + + if(decoratorSeeds.isEmpty()) { + Log.error("Finished dungeon search with no seeds."); + return; + } + + dataStorage.getTimeMachine().structureSeeds = new ArrayList<>(); + LCG failedDungeon = LCG.JAVA.combine(-5); + + for(long decoratorSeed: decoratorSeeds) { + for(int i = 0; i < 8; i++) { + ChunkRandomReverser.reversePopulationSeed((decoratorSeed ^ LCG.JAVA.multiplier) + - this.feature.getConfig().getSalt(this.biome), + this.chunkX << 4, this.chunkZ << 4, SeedCracker.MC_VERSION).forEach(structureSeed -> { + Log.printSeed("Found structure seed ${SEED}.", structureSeed); + dataStorage.getTimeMachine().structureSeeds.add(structureSeed); + }); + + decoratorSeed = failedDungeon.nextSeed(decoratorSeed); + } + } + + dataStorage.getTimeMachine().poke(TimeMachine.Phase.BIOMES); + } + } + +} \ No newline at end of file diff --git a/src/main/java/kaptainwutax/seedcracker/cracker/decorator/EmeraldOre.java b/src/main/java/kaptainwutax/seedcracker/cracker/decorator/EmeraldOre.java new file mode 100644 index 00000000..c1b584f8 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/cracker/decorator/EmeraldOre.java @@ -0,0 +1,83 @@ +package kaptainwutax.seedcracker.cracker.decorator; + +import kaptainwutax.biomeutils.Biome; +import kaptainwutax.seedutils.mc.ChunkRand; +import kaptainwutax.seedutils.mc.Dimension; +import kaptainwutax.seedutils.mc.MCVersion; +import kaptainwutax.seedutils.mc.VersionMap; + +public class EmeraldOre extends Decorator { + + public static final VersionMap CONFIGS = new VersionMap() + .add(MCVersion.v1_13, new Decorator.Config(4, 14)) + .add(MCVersion.v1_16, new EmeraldOre.Config(6, 14)); + + public EmeraldOre(MCVersion version) { + super(CONFIGS.getAsOf(version), version); + } + + public EmeraldOre(Decorator.Config config) { + super(config, null); + } + + @Override + public String getName() { + return "emerald_ore"; + } + + @Override + public boolean canStart(Data data, long structureSeed, ChunkRand rand) { + super.canStart(data, structureSeed, rand); + + int bound = rand.nextInt(6) + 3; + + for(int i = 0; i < bound; i++) { + int x, y, z; + + if(this.getVersion().isOlderThan(MCVersion.v1_15)) { + x = rand.nextInt(16); + y = rand.nextInt(28) + 4; + z = rand.nextInt(16); + } else { + x = rand.nextInt(16); + z = rand.nextInt(16); + y = rand.nextInt(28) + 4; + } + + if(y == data.blockY && x == data.offsetX && z == data.offsetZ) { + return true; + } + } + + return false; + } + + @Override + public boolean isValidDimension(Dimension dimension) { + return dimension == Dimension.OVERWORLD; + } + + @Override + public boolean isValidBiome(Biome biome) { + return biome == Biome.GRAVELLY_MOUNTAINS || biome == Biome.MODIFIED_GRAVELLY_MOUNTAINS + || biome == Biome.MOUNTAINS || biome == Biome.WOODED_MOUNTAINS || biome == Biome.MOUNTAIN_EDGE; + } + + public EmeraldOre.Data at(int blockX, int blockY, int blockZ, Biome biome) { + return new EmeraldOre.Data(this, blockX, blockY, blockZ, biome); + } + + public static class Data extends Decorator.Data { + public final int offsetX; + public final int blockY; + public final int offsetZ; + + public Data(EmeraldOre feature, int blockX, int blockY, int blockZ, Biome biome) { + super(feature, blockX >> 4, blockZ >> 4, biome); + this.offsetX = blockX & 15; + this.blockY = blockY; + this.offsetZ = blockZ & 15; + } + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/cracker/storage/DataStorage.java b/src/main/java/kaptainwutax/seedcracker/cracker/storage/DataStorage.java new file mode 100644 index 00000000..eaf6383c --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/cracker/storage/DataStorage.java @@ -0,0 +1,204 @@ +package kaptainwutax.seedcracker.cracker.storage; + +import io.netty.util.internal.ConcurrentSet; +import kaptainwutax.featureutils.Feature; +import kaptainwutax.featureutils.decorator.DesertWell; +import kaptainwutax.featureutils.decorator.EndGateway; +import kaptainwutax.featureutils.structure.BuriedTreasure; +import kaptainwutax.featureutils.structure.Structure; +import kaptainwutax.featureutils.structure.TriangularStructure; +import kaptainwutax.featureutils.structure.UniformStructure; +import kaptainwutax.seedcracker.cracker.BiomeData; +import kaptainwutax.seedcracker.cracker.DataAddedEvent; +import kaptainwutax.seedcracker.cracker.HashedSeedData; +import kaptainwutax.seedcracker.cracker.PillarData; +import kaptainwutax.seedcracker.cracker.decorator.Dungeon; +import kaptainwutax.seedcracker.cracker.decorator.EmeraldOre; + +import java.util.Comparator; +import java.util.Set; +import java.util.function.Consumer; + +public class DataStorage { + + public static final Comparator>> SEED_DATA_COMPARATOR = (s1, s2) -> { + boolean isStructure1 = s1.data.feature instanceof Structure; + boolean isStructure2 = s2.data.feature instanceof Structure; + + //Structures always come before decorators. + if(isStructure1 != isStructure2) { + return isStructure2 ? 1: -1; + } + + if(s1.equals(s2)) { + return 0; + } + + double diff = getBits(s2.data.feature) - getBits(s1.data.feature); + return diff == 0 ? 1 : (int)Math.signum(diff); + }; + + protected TimeMachine timeMachine = new TimeMachine(this); + protected Set> scheduledData = new ConcurrentSet<>(); + + protected PillarData pillarData = null; + protected ScheduledSet>> baseSeedData = new ScheduledSet<>(SEED_DATA_COMPARATOR); + protected ScheduledSet> biomeSeedData = new ScheduledSet<>(null); + protected HashedSeedData hashedSeedData = null; + + public void tick() { + if(!this.timeMachine.isRunning) { + this.baseSeedData.dump(); + this.biomeSeedData.dump(); + + this.timeMachine.isRunning = true; + + TimeMachine.SERVICE.submit(() -> { + try { + this.scheduledData.removeIf(c -> { + c.accept(this); + return true; + }); + } catch(Exception e) { + e.printStackTrace(); + } + + this.timeMachine.isRunning = false; + }); + } + } + + public synchronized boolean addPillarData(PillarData data, DataAddedEvent event) { + boolean isAdded = this.pillarData == null; + + if(isAdded && data != null) { + this.pillarData = data; + this.schedule(event::onDataAdded); + } + + return isAdded; + } + + public synchronized boolean addBaseData(Feature.Data data, DataAddedEvent event) { + Entry> e = new Entry<>(data, event); + + if(this.baseSeedData.contains(e)) { + return false; + } + + this.baseSeedData.scheduleAdd(e); + this.schedule(event::onDataAdded); + return true; + } + + public synchronized boolean addBiomeData(BiomeData data, DataAddedEvent event) { + Entry e = new Entry<>(data, event); + + if(this.biomeSeedData.contains(e)) { + return false; + } + + this.biomeSeedData.scheduleAdd(e); + this.schedule(event::onDataAdded); + return true; + } + + public synchronized boolean addHashedSeedData(HashedSeedData data, DataAddedEvent event) { + if(this.hashedSeedData == null || this.hashedSeedData.getHashedSeed() != data.getHashedSeed()) { + this.hashedSeedData = data; + this.schedule(event::onDataAdded); + return true; + } + + return false; + } + + public void schedule(Consumer consumer) { + this.scheduledData.add(consumer); + } + + public TimeMachine getTimeMachine() { + return this.timeMachine; + } + + public double getBaseBits() { + double bits = 0.0D; + + for(Entry> e: this.baseSeedData) { + bits += getBits(e.data.feature); + } + + return bits; + } + + public double getWantedBits() { + return 32.0D; + } + + public static double getBits(Feature feature) { + if(feature instanceof UniformStructure) { + UniformStructure s = (UniformStructure)feature; + return Math.log(s.getOffset() * s.getOffset()) / Math.log(2); + } else if(feature instanceof TriangularStructure) { + TriangularStructure s = (TriangularStructure)feature; + return Math.log(s.getPeak() * s.getPeak()) / Math.log(2); + } + + if(feature instanceof BuriedTreasure)return Math.log(100) / Math.log(2); + if(feature instanceof DesertWell)return Math.log(1000 * 16 * 16) / Math.log(2); + if(feature instanceof Dungeon)return Math.log(256 * 16 * 16 * 0.125D) / Math.log(2); + if(feature instanceof EmeraldOre)return Math.log(28 * 16 * 16 * 0.5D) / Math.log(2); + if(feature instanceof EndGateway)return Math.log(700 * 16 * 16 * 7) / Math.log(2); + + throw new UnsupportedOperationException("go do implement bits count for " + feature.getName() + " you fool"); + } + + public void clear() { + this.scheduledData = new ConcurrentSet<>(); + this.pillarData = null; + this.baseSeedData = new ScheduledSet<>(SEED_DATA_COMPARATOR); + this.biomeSeedData = new ScheduledSet<>(null); + this.hashedSeedData = null; + this.timeMachine.shouldTerminate = true; + this.timeMachine = new TimeMachine(this); + } + + public static class Entry { + public final T data; + public final DataAddedEvent event; + + public Entry(T data, DataAddedEvent event) { + this.data = data; + this.event = event; + } + + @Override + public boolean equals(Object o) { + if(this == o)return true; + if(!(o instanceof Entry))return false; + Entry entry = (Entry)o; + + if(this.data instanceof Feature.Data && entry.data instanceof Feature.Data) { + Feature.Data d1 = (Feature.Data)this.data; + Feature.Data d2 = (Feature.Data)entry.data; + return d1.feature == d2.feature && d1.chunkX == d2.chunkX && d1.chunkZ == d2.chunkZ; + } else if(this.data instanceof BiomeData && entry.data instanceof BiomeData) { + return this.data.equals(entry.data); + } + + return false; + } + + @Override + public int hashCode() { + if(this.data instanceof Feature.Data) { + return ((Feature.Data)this.data).chunkX * 31 + ((Feature.Data)this.data).chunkZ; + } else if(this.data instanceof BiomeData) { + return this.data.hashCode(); + } + + return super.hashCode(); + } + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/cracker/storage/ProgressListener.java b/src/main/java/kaptainwutax/seedcracker/cracker/storage/ProgressListener.java new file mode 100644 index 00000000..8f31e193 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/cracker/storage/ProgressListener.java @@ -0,0 +1,27 @@ +package kaptainwutax.seedcracker.cracker.storage; + +import kaptainwutax.seedcracker.util.Log; + +public class ProgressListener { + + protected float progress; + protected int count = 0; + + public ProgressListener() { + this(0.0F); + } + + public ProgressListener(float progress) { + this.progress = progress; + } + + public synchronized void addPercent(float percent, boolean debug) { + if((this.count & 3) == 0 && debug) { + Log.debug("Progress: " + this.progress + "%"); + } + + this.count++; + this.progress += percent; + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/cracker/storage/ScheduledSet.java b/src/main/java/kaptainwutax/seedcracker/cracker/storage/ScheduledSet.java new file mode 100644 index 00000000..46c36fcb --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/cracker/storage/ScheduledSet.java @@ -0,0 +1,48 @@ +package kaptainwutax.seedcracker.cracker.storage; + +import java.util.*; + +public class ScheduledSet implements Iterable { + + protected final Set baseSet; + protected final Set scheduledSet; + + public ScheduledSet(Comparator comparator) { + if(comparator != null) { + this.baseSet = new TreeSet<>(comparator); + } else { + this.baseSet = new HashSet<>(); + } + + this.scheduledSet = new HashSet<>(); + } + + public synchronized void scheduleAdd(T e) { + this.scheduledSet.add(e); + } + + public synchronized void dump() { + synchronized(this.baseSet) { + this.baseSet.addAll(this.scheduledSet); + this.scheduledSet.clear(); + } + } + + public synchronized boolean contains(T e) { + return this.baseSet.contains(e) || this.scheduledSet.contains(e); + } + + public Set getBaseSet() { + return this.baseSet; + } + + @Override + public synchronized Iterator iterator() { + return this.baseSet.iterator(); + } + + public synchronized int size() { + return this.baseSet.size(); + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/cracker/storage/TimeMachine.java b/src/main/java/kaptainwutax/seedcracker/cracker/storage/TimeMachine.java new file mode 100644 index 00000000..beb6c3f1 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/cracker/storage/TimeMachine.java @@ -0,0 +1,245 @@ +package kaptainwutax.seedcracker.cracker.storage; + +import kaptainwutax.biomeutils.source.OverworldBiomeSource; +import kaptainwutax.featureutils.Feature; +import kaptainwutax.seedcracker.SeedCracker; +import kaptainwutax.seedcracker.cracker.BiomeData; +import kaptainwutax.seedcracker.util.Log; +import kaptainwutax.seedutils.lcg.LCG; +import kaptainwutax.seedutils.mc.ChunkRand; +import kaptainwutax.seedutils.mc.seed.WorldSeed; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; + +public class TimeMachine { + + public static ExecutorService SERVICE = Executors.newFixedThreadPool(5); + + private LCG inverseLCG = LCG.JAVA.combine(-2); + protected DataStorage dataStorage; + + public boolean isRunning = false; + public boolean shouldTerminate = false; + + public List pillarSeeds = null; + public List structureSeeds = null; + public List worldSeeds = null; + + public TimeMachine(DataStorage dataStorage) { + this.dataStorage = dataStorage; + } + + public void poke(Phase phase) { + this.isRunning = true; + + final Phase[] finalPhase = {phase}; + + while(finalPhase[0] != null && !this.shouldTerminate) { + if(finalPhase[0] == Phase.PILLARS) { + if(!this.pokePillars())break; + } else if(finalPhase[0] == Phase.STRUCTURES) { + if(!this.pokeStructures())break; + } else if(finalPhase[0] == Phase.BIOMES) { + if(!this.pokeBiomes())break; + } + + finalPhase[0] = finalPhase[0].nextPhase(); + } + } + + protected boolean pokePillars() { + if(this.pillarSeeds != null || this.dataStorage.pillarData == null)return false; + this.pillarSeeds = new ArrayList<>(); + + Log.debug("===================================="); + Log.warn("Looking for pillar seeds..."); + + for(int pillarSeed = 0; pillarSeed < 1 << 16 && !this.shouldTerminate; pillarSeed++) { + if(this.dataStorage.pillarData.test(pillarSeed)) { + Log.printSeed("Found pillar seed ${SEED}.", pillarSeed); + this.pillarSeeds.add(pillarSeed); + } + } + + if(!this.pillarSeeds.isEmpty()) { + Log.warn("Finished searching for pillar seeds."); + } else { + Log.error("Finished search with no results."); + } + + return true; + } + + protected boolean pokeStructures() { + if(this.pillarSeeds == null || this.structureSeeds != null || + this.dataStorage.getBaseBits() < this.dataStorage.getWantedBits())return false; + + this.structureSeeds = new ArrayList<>(); + + Feature.Data[] cache = new Feature.Data[this.dataStorage.baseSeedData.size()]; + int id = 0; + + for(DataStorage.Entry> entry: this.dataStorage.baseSeedData) { + cache[id++] = entry.data; + } + + for(int pillarSeed: this.pillarSeeds) { + Log.debug("===================================="); + Log.warn("Looking for structure seeds with pillar seed [" + pillarSeed + "]..."); + + AtomicInteger completion = new AtomicInteger(); + ProgressListener progressListener = new ProgressListener(); + + for(int threadId = 0; threadId < 4; threadId++) { + int fThreadId = threadId; + + SERVICE.submit(() -> { + ChunkRand rand = new ChunkRand(); + + long lower = (long)fThreadId * (1L << 30); + long upper = (long)(fThreadId + 1) * (1L << 30); + + for(long partialWorldSeed = lower; partialWorldSeed < upper && !this.shouldTerminate; partialWorldSeed++) { + if((partialWorldSeed & ((1 << 27) - 1)) == 0) { + progressListener.addPercent(3.125F, true); + } + + long seed = this.timeMachine(partialWorldSeed, pillarSeed); + + boolean matches = true; + + for(Feature.Data baseSeedDatum: cache) { + if(!baseSeedDatum.testStart(seed, rand)) { + matches = false; + break; + } + } + + if(matches) { + this.structureSeeds.add(seed); + Log.printSeed("Found structure seed ${SEED}.", seed); + } + } + + completion.getAndIncrement(); + }); + } + + while(completion.get() != 4) { + try {Thread.sleep(50);} + catch(InterruptedException e) {e.printStackTrace();} + + if(this.shouldTerminate) { + return false; + } + } + + progressListener.addPercent(0.0F, true); + } + + if(!this.structureSeeds.isEmpty()) { + Log.warn("Finished searching for structure seeds."); + } else { + Log.error("Finished search with no results."); + } + + return true; + } + + protected boolean pokeBiomes() { + if(this.structureSeeds == null || this.worldSeeds != null)return false; + if(this.dataStorage.hashedSeedData == null && + (this.dataStorage.biomeSeedData.size() < 5 || this.structureSeeds.size() > 20))return false; + + this.worldSeeds = new ArrayList<>(); + Log.debug("===================================="); + + if(this.dataStorage.hashedSeedData != null) { + Log.warn("Looking for world seeds..."); + + for(long structureSeed: this.structureSeeds) { + WorldSeed.fromHash(structureSeed, this.dataStorage.hashedSeedData.getHashedSeed()).forEach(worldSeed -> { + this.worldSeeds.add(worldSeed); + Log.printSeed("Found world seed ${SEED}.", worldSeed); + }); + + if(this.shouldTerminate) { + return false; + } + } + + if(!this.worldSeeds.isEmpty()) { + Log.warn("Finished searching for world seeds."); + return true; + } else { + Log.error("Finished search with no results, reverting back to biomes."); + } + } + + Log.warn("Looking for world seeds..."); + + for(long structureSeed : this.structureSeeds) { + for(long upperBits = 0; upperBits < 1 << 16 && !this.shouldTerminate; upperBits++) { + long worldSeed = (upperBits << 48) | structureSeed; + + OverworldBiomeSource source = new OverworldBiomeSource(SeedCracker.MC_VERSION, worldSeed); + + boolean matches = true; + + for(DataStorage.Entry e: this.dataStorage.biomeSeedData) { + if(!e.data.test(source)) { + matches = false; + break; + } + } + + if(matches) { + this.worldSeeds.add(worldSeed); + Log.printSeed("Found world seed ${SEED}.", worldSeed); + } + + if(this.shouldTerminate) { + return false; + } + } + } + + if(!this.worldSeeds.isEmpty()) { + Log.warn("Finished searching for world seeds."); + } else { + Log.error("Finished search with no results."); + } + + return true; + } + + public long timeMachine(long partialWorldSeed, int pillarSeed) { + long currentSeed = 0L; + currentSeed |= (partialWorldSeed & 0xFFFF0000L) << 16; + currentSeed |= (long)pillarSeed << 16; + currentSeed |= partialWorldSeed & 0xFFFFL; + + currentSeed = this.inverseLCG.nextSeed(currentSeed); + currentSeed ^= LCG.JAVA.multiplier; + return currentSeed; + } + + public enum Phase { + BIOMES(null), STRUCTURES(BIOMES), PILLARS(STRUCTURES); + + private final Phase nextPhase; + + Phase(Phase nextPhase) { + this.nextPhase = nextPhase; + } + + public Phase nextPhase() { + return this.nextPhase; + } + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/finder/BiomeFinder.java b/src/main/java/kaptainwutax/seedcracker/finder/BiomeFinder.java new file mode 100644 index 00000000..11224d96 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/finder/BiomeFinder.java @@ -0,0 +1,69 @@ +package kaptainwutax.seedcracker.finder; + +import kaptainwutax.seedcracker.SeedCracker; +import kaptainwutax.seedcracker.cracker.BiomeData; +import kaptainwutax.seedcracker.cracker.DataAddedEvent; +import kaptainwutax.seedcracker.render.Color; +import kaptainwutax.seedcracker.render.Cube; +import kaptainwutax.seedcracker.util.BiomeFixer; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.Heightmap; +import net.minecraft.world.World; +import net.minecraft.world.biome.Biome; +import net.minecraft.world.biome.Biomes; +import net.minecraft.world.dimension.DimensionType; + +import java.util.ArrayList; +import java.util.List; + +public class BiomeFinder extends Finder { + + public BiomeFinder(World world, ChunkPos chunkPos) { + super(world, chunkPos); + } + + @Override + public List findInChunk() { + List result = new ArrayList<>(); + + for(int x = 0; x < 16; x += 4) { + for(int z = 0; z < 16; z += 4) { + BlockPos blockPos = this.chunkPos.getCenterBlockPos().add(x, 0, z); + Biome biome = this.world.getBiomeForNoiseGen(blockPos.getX() >> 2, 0, blockPos.getZ() >> 2); + + //TODO: Fix this multi-threading issue. + if(biome == Biomes.THE_VOID) { + continue; + } + + kaptainwutax.biomeutils.Biome otherBiome = BiomeFixer.swap(biome); + + BiomeData data = new BiomeData(otherBiome, blockPos.getX() >> 2, blockPos.getZ() >> 2); + + if(SeedCracker.get().getDataStorage().addBiomeData(data, DataAddedEvent.POKE_BIOMES)) { + blockPos = this.world.getTopPosition(Heightmap.Type.WORLD_SURFACE, blockPos).down(); + result.add(blockPos); + } + } + } + + result.forEach(pos -> { + this.renderers.add(new Cube(pos, new Color(51, 204, 128))); + }); + + return result; + } + + @Override + public boolean isValidDimension(DimensionType dimension) { + return this.isOverworld(dimension); + } + + public static List create(World world, ChunkPos chunkPos) { + List finders = new ArrayList<>(); + finders.add(new BiomeFinder(world, chunkPos)); + return finders; + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/finder/BlockFinder.java b/src/main/java/kaptainwutax/seedcracker/finder/BlockFinder.java new file mode 100644 index 00000000..4d5f3514 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/finder/BlockFinder.java @@ -0,0 +1,44 @@ +package kaptainwutax.seedcracker.finder; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.World; +import net.minecraft.world.chunk.Chunk; + +import java.util.*; +import java.util.stream.Collectors; + +public abstract class BlockFinder extends Finder { + + private Set targetBlockStates = new HashSet<>(); + protected List searchPositions = new ArrayList<>(); + + public BlockFinder(World world, ChunkPos chunkPos, Block block) { + super(world, chunkPos); + this.targetBlockStates.addAll(block.getStateManager().getStates()); + } + + public BlockFinder(World world, ChunkPos chunkPos, BlockState... blockStates) { + super(world, chunkPos); + this.targetBlockStates.addAll(Arrays.stream(blockStates).collect(Collectors.toList())); + } + + @Override + public List findInChunk() { + List result = new ArrayList<>(); + Chunk chunk = this.world.getChunk(this.chunkPos.getCenterBlockPos()); + + for(BlockPos blockPos: this.searchPositions) { + BlockState currentState = chunk.getBlockState(blockPos); + + if(this.targetBlockStates.contains(currentState)) { + result.add(this.chunkPos.getCenterBlockPos().add(blockPos)); + } + } + + return result; + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/finder/Finder.java b/src/main/java/kaptainwutax/seedcracker/finder/Finder.java new file mode 100644 index 00000000..11785fa3 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/finder/Finder.java @@ -0,0 +1,154 @@ +package kaptainwutax.seedcracker.finder; + +import kaptainwutax.seedcracker.finder.decorator.DesertWellFinder; +import kaptainwutax.seedcracker.finder.decorator.DungeonFinder; +import kaptainwutax.seedcracker.finder.decorator.EndGatewayFinder; +import kaptainwutax.seedcracker.finder.decorator.EndPillarsFinder; +import kaptainwutax.seedcracker.finder.decorator.ore.EmeraldOreFinder; +import kaptainwutax.seedcracker.finder.structure.*; +import kaptainwutax.seedcracker.render.Renderer; +import net.minecraft.client.MinecraftClient; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import net.minecraft.world.dimension.DimensionType; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +public abstract class Finder { + + protected static final List CHUNK_POSITIONS = new ArrayList<>(); + protected static final List SUB_CHUNK_POSITIONS = new ArrayList<>(); + + protected MinecraftClient mc = MinecraftClient.getInstance(); + protected List renderers = new ArrayList<>(); + protected World world; + protected ChunkPos chunkPos; + + static { + for(int x = 0; x < 16; x++) { + for(int z = 0; z < 16; z++) { + for(int y = 0; y < 256; y++) { + BlockPos pos = new BlockPos(x, y, z); + if(y < 16)SUB_CHUNK_POSITIONS.add(pos); + CHUNK_POSITIONS.add(pos); + } + } + } + } + + public Finder(World world, ChunkPos chunkPos) { + this.world = world; + this.chunkPos = chunkPos; + } + + public World getWorld() { + return this.world; + } + + public ChunkPos getChunkPos() { + return this.chunkPos; + } + + public abstract List findInChunk(); + + public boolean shouldRender() { + DimensionType finderDim = this.world.getDimension(); + DimensionType playerDim = mc.player.world.getDimension(); + + if(finderDim != playerDim)return false; + + int renderDistance = mc.options.viewDistance * 16 + 16; + Vec3d playerPos = mc.player.getPos(); + + for(Renderer renderer: this.renderers) { + BlockPos pos = renderer.getPos(); + double distance = playerPos.squaredDistanceTo(pos.getX(), playerPos.y, pos.getZ()); + if(distance <= renderDistance * renderDistance + 32)return true; + } + + return false; + } + + public void render() { + this.renderers.forEach(Renderer::render); + } + + public boolean isUseless() { + return this.renderers.isEmpty(); + } + + public abstract boolean isValidDimension(DimensionType dimension); + + public boolean isOverworld(DimensionType dimension) { + return ((DimensionTypeCaller)dimension).getInfiniburn().getPath().endsWith("overworld"); + } + + public boolean isNether(DimensionType dimension) { + return ((DimensionTypeCaller)dimension).getInfiniburn().getPath().endsWith("nether"); + } + + public boolean isEnd(DimensionType dimension) { + return ((DimensionTypeCaller)dimension).getInfiniburn().getPath().endsWith("end"); + } + + public static List buildSearchPositions(List base, Predicate removeIf) { + List newList = new ArrayList<>(); + + for(BlockPos pos: base) { + if(!removeIf.test(pos)) { + newList.add(pos); + } + } + + return newList; + } + + public enum Category { + STRUCTURES, + DECORATORS, + BIOMES, + } + + public enum Type { + BURIED_TREASURE(BuriedTreasureFinder::create, Category.STRUCTURES), + DESERT_TEMPLE(DesertPyramidFinder::create, Category.STRUCTURES), + END_CITY(EndCityFinder::create, Category.STRUCTURES), + //IGLOO(IglooFinder::create, Category.STRUCTURES), + JUNGLE_TEMPLE(JunglePyramidFinder::create, Category.STRUCTURES), + MONUMENT(MonumentFinder::create, Category.STRUCTURES), + SWAMP_HUT(SwampHutFinder::create, Category.STRUCTURES), + //MANSION(MansionFinder::create, Category.STRUCTURES), + SHIPWRECK(ShipwreckFinder::create, Category.STRUCTURES), + + END_PILLARS(EndPillarsFinder::create, Category.DECORATORS), + END_GATEWAY(EndGatewayFinder::create, Category.DECORATORS), + DUNGEON(DungeonFinder::create, Category.DECORATORS), + EMERALD_ORE(EmeraldOreFinder::create, Category.DECORATORS), + DESERT_WELL(DesertWellFinder::create, Category.DECORATORS), + BIOME(BiomeFinder::create, Category.BIOMES); + + public final FinderBuilder finderBuilder; + private final Category category; + + Type(FinderBuilder finderBuilder, Category category) { + this.finderBuilder = finderBuilder; + this.category = category; + } + + public static List getForCategory(Category category) { + return Arrays.stream(values()).filter(type -> type.category == category).collect(Collectors.toList()); + } + } + + public interface DimensionTypeCaller { + Identifier getInfiniburn(); + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/finder/FinderBuilder.java b/src/main/java/kaptainwutax/seedcracker/finder/FinderBuilder.java new file mode 100644 index 00000000..e8e52111 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/finder/FinderBuilder.java @@ -0,0 +1,13 @@ +package kaptainwutax.seedcracker.finder; + +import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.World; + +import java.util.List; + +@FunctionalInterface +public interface FinderBuilder { + + List build(World world, ChunkPos chunkPos); + +} diff --git a/src/main/java/kaptainwutax/seedcracker/finder/FinderQueue.java b/src/main/java/kaptainwutax/seedcracker/finder/FinderQueue.java new file mode 100644 index 00000000..a39e02b4 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/finder/FinderQueue.java @@ -0,0 +1,83 @@ +package kaptainwutax.seedcracker.finder; + +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.systems.RenderSystem; +import kaptainwutax.seedcracker.SeedCracker; +import kaptainwutax.seedcracker.profile.FinderConfig; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.World; + +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class FinderQueue { + + private final static FinderQueue INSTANCE = new FinderQueue(); + public static ExecutorService SERVICE = Executors.newFixedThreadPool(5); + + public RenderType renderType = RenderType.XRAY; + public FinderConfig finderProfile = new FinderConfig(); + + private FinderQueue() { + this.clear(); + } + + public static FinderQueue get() { + return INSTANCE; + } + + public void onChunkData(World world, ChunkPos chunkPos) { + if(!SeedCracker.get().isActive())return; + + this.finderProfile.getActiveFinderTypes().forEach(type -> { + SERVICE.submit(() -> { + try { + List finders = type.finderBuilder.build(world, chunkPos); + + finders.forEach(finder -> { + if(finder.isValidDimension(world.getDimension())) { + finder.findInChunk(); + this.finderProfile.addFinder(type, finder); + } + }); + } catch(Exception e) { + e.printStackTrace(); + } + }); + }); + } + + public void renderFinders(MatrixStack matrixStack) { + if(this.renderType == RenderType.OFF)return; + + RenderSystem.pushMatrix(); + RenderSystem.multMatrix(matrixStack.peek().getModel()); + + GlStateManager.disableTexture(); + + //Makes it render through blocks. + if(this.renderType == RenderType.XRAY) { + GlStateManager.disableDepthTest(); + } + + this.finderProfile.getActiveFinders().forEach(finder -> { + if(finder.shouldRender()) { + finder.render(); + } + }); + + RenderSystem.popMatrix(); + } + + public void clear() { + this.renderType = RenderType.XRAY; + this.finderProfile = new FinderConfig(); + } + + public enum RenderType { + OFF, ON, XRAY + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/finder/decorator/DesertWellFinder.java b/src/main/java/kaptainwutax/seedcracker/finder/decorator/DesertWellFinder.java new file mode 100644 index 00000000..326a022c --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/finder/decorator/DesertWellFinder.java @@ -0,0 +1,104 @@ +package kaptainwutax.seedcracker.finder.decorator; + +import kaptainwutax.featureutils.decorator.DesertWell; +import kaptainwutax.seedcracker.Features; +import kaptainwutax.seedcracker.SeedCracker; +import kaptainwutax.seedcracker.cracker.DataAddedEvent; +import kaptainwutax.seedcracker.finder.Finder; +import kaptainwutax.seedcracker.finder.structure.PieceFinder; +import kaptainwutax.seedcracker.render.Color; +import kaptainwutax.seedcracker.render.Cube; +import kaptainwutax.seedcracker.render.Cuboid; +import kaptainwutax.seedcracker.util.BiomeFixer; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3i; +import net.minecraft.world.World; +import net.minecraft.world.biome.Biome; +import net.minecraft.world.dimension.DimensionType; + +import java.util.ArrayList; +import java.util.List; + +public class DesertWellFinder extends PieceFinder { + + protected static List SEARCH_POSITIONS = buildSearchPositions(CHUNK_POSITIONS, pos -> { + return false; + }); + + protected static Vec3i SIZE = new Vec3i(5, 6, 5); + + public DesertWellFinder(World world, ChunkPos chunkPos) { + super(world, chunkPos, Direction.NORTH, SIZE); + this.searchPositions = SEARCH_POSITIONS; + this.buildStructure(); + } + + @Override + public List findInChunk() { + Biome biome = this.world.getBiomeForNoiseGen((this.chunkPos.x << 2) + 2, 0, (this.chunkPos.z << 2) + 2); + + if(!Features.DESERT_WELL.isValidBiome(BiomeFixer.swap(biome))) { + return new ArrayList<>(); + } + + List result = super.findInChunk(); + + result.forEach(pos -> { + pos = pos.add(2, 1, 2); + + DesertWell.Data data = Features.DESERT_WELL.at(pos.getX(), pos.getZ()); + + if(SeedCracker.get().getDataStorage().addBaseData(data, DataAddedEvent.POKE_STRUCTURES)) { + this.renderers.add(new Cuboid(pos.add(-2, -1, -2), SIZE, new Color(128, 128, 255))); + this.renderers.add(new Cube(pos, new Color(128, 128, 255))); + } + }); + + return result; + } + + @Override + public boolean isValidDimension(DimensionType dimension) { + return this.isOverworld(dimension); + } + + protected void buildStructure() { + BlockState sandstone = Blocks.SANDSTONE.getDefaultState(); + BlockState sandstoneSlab = Blocks.SANDSTONE_SLAB.getDefaultState(); + BlockState water = Blocks.WATER.getDefaultState(); + + this.fillWithOutline(0, 0, 0, 4, 1, 4, sandstone, sandstone, false); + this.fillWithOutline(1, 5, 1, 3, 5, 3, sandstoneSlab, sandstoneSlab, false); + this.addBlock(sandstone, 2, 5, 2); + + BlockPos p1 = new BlockPos(2, 1, 2); + this.addBlock(water, p1.getX(), p1.getY(), p1.getZ()); + + Direction.Type.HORIZONTAL.forEach(facing -> { + BlockPos p2 = p1.offset(facing); + this.addBlock(water, p2.getX(), p2.getY(), p2.getZ()); + }); + } + + public static List create(World world, ChunkPos chunkPos) { + List finders = new ArrayList<>(); + finders.add(new DesertWellFinder(world, chunkPos)); + + finders.add(new DesertWellFinder(world, new ChunkPos(chunkPos.x - 1, chunkPos.z))); + finders.add(new DesertWellFinder(world, new ChunkPos(chunkPos.x, chunkPos.z - 1))); + finders.add(new DesertWellFinder(world, new ChunkPos(chunkPos.x - 1, chunkPos.z - 1))); + + finders.add(new DesertWellFinder(world, new ChunkPos(chunkPos.x + 1, chunkPos.z))); + finders.add(new DesertWellFinder(world, new ChunkPos(chunkPos.x, chunkPos.z + 1))); + finders.add(new DesertWellFinder(world, new ChunkPos(chunkPos.x + 1, chunkPos.z + 1))); + + finders.add(new DesertWellFinder(world, new ChunkPos(chunkPos.x - 1, chunkPos.z - 1))); + finders.add(new DesertWellFinder(world, new ChunkPos(chunkPos.x - 1, chunkPos.z + 1))); + return finders; + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/finder/decorator/DungeonFinder.java b/src/main/java/kaptainwutax/seedcracker/finder/decorator/DungeonFinder.java new file mode 100644 index 00000000..d5f91dca --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/finder/decorator/DungeonFinder.java @@ -0,0 +1,144 @@ +package kaptainwutax.seedcracker.finder.decorator; + +import kaptainwutax.seedcracker.Features; +import kaptainwutax.seedcracker.SeedCracker; +import kaptainwutax.seedcracker.cracker.decorator.Dungeon; +import kaptainwutax.seedcracker.finder.BlockFinder; +import kaptainwutax.seedcracker.finder.Finder; +import kaptainwutax.seedcracker.render.Color; +import kaptainwutax.seedcracker.render.Cube; +import kaptainwutax.seedcracker.render.Cuboid; +import kaptainwutax.seedcracker.util.BiomeFixer; +import kaptainwutax.seedcracker.util.Log; +import kaptainwutax.seedcracker.util.PosIterator; +import net.minecraft.block.Block; +import net.minecraft.block.Blocks; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.MobSpawnerBlockEntity; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.math.Vec3i; +import net.minecraft.world.World; +import net.minecraft.world.biome.Biome; +import net.minecraft.world.dimension.DimensionType; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +public class DungeonFinder extends BlockFinder { + + protected static List SEARCH_POSITIONS = buildSearchPositions(CHUNK_POSITIONS, pos -> { + return false; + }); + + protected static Set REQUIRED_FLOOR_POSITIONS = PosIterator.create( + new BlockPos(-3, -1, -3), + new BlockPos(3, -1, 3) + ); + + public DungeonFinder(World world, ChunkPos chunkPos) { + super(world, chunkPos, Blocks.SPAWNER); + this.searchPositions = SEARCH_POSITIONS; + } + + @Override + public List findInChunk() { + //Gets all the positions with a mob spawner in the chunk. + List result = super.findInChunk(); + + if(result.size() != 1)return new ArrayList<>(); + + result.removeIf(pos -> { + BlockEntity blockEntity = this.world.getBlockEntity(pos); + if(!(blockEntity instanceof MobSpawnerBlockEntity))return true; + + for(BlockPos blockPos: REQUIRED_FLOOR_POSITIONS) { + BlockPos currentPos = pos.add(blockPos); + Block currentBlock = this.world.getBlockState(currentPos).getBlock(); + + if(currentBlock == Blocks.COBBLESTONE) {} + else if(currentBlock == Blocks.MOSSY_COBBLESTONE) {} + else return true; + } + + return false; + }); + + if(result.size() != 1)return new ArrayList<>(); + Biome biome = this.world.getBiomeForNoiseGen((this.chunkPos.x << 2) + 2, 0, (this.chunkPos.z << 2) + 2); + + BlockPos pos = result.get(0); + Vec3i size = this.getDungeonSize(pos); + int[] floorCalls = this.getFloorCalls(size, pos); + + Dungeon.Data data = Features.DUNGEON.at(pos.getX(), pos.getY(), pos.getZ(), size, floorCalls, BiomeFixer.swap(biome)); + + if(SeedCracker.get().getDataStorage().addBaseData(data, data::onDataAdded)) { + this.renderers.add(new Cube(pos, new Color(255, 0, 0))); + + if(data.usesFloor()) { + this.renderers.add(new Cuboid(pos.subtract(size), pos.add(size).add(1, -1, 1), new Color(255, 0, 0))); + } + } + + return result; + } + + public Vec3i getDungeonSize(BlockPos spawnerPos) { + for(int xo = 4; xo >= 3; xo--) { + for(int zo = 4; zo >= 3; zo--) { + Block block = this.world.getBlockState(spawnerPos.add(xo, -1, zo)).getBlock(); + if(block == Blocks.MOSSY_COBBLESTONE || block == Blocks.COBBLESTONE)return new Vec3i(xo, 0, zo); + } + } + + return Vec3i.ZERO; + } + + public int[] getFloorCalls(Vec3i dungeonSize, BlockPos spawnerPos) { + int[] floorCalls = new int[(dungeonSize.getX() * 2 + 1) * (dungeonSize.getZ() * 2 + 1)]; + int i = 0; + + for(int xo = -dungeonSize.getX(); xo <= dungeonSize.getX(); xo++) { + for(int zo = -dungeonSize.getZ(); zo <= dungeonSize.getZ(); zo++) { + Block block = this.world.getBlockState(spawnerPos.add(xo, -1, zo)).getBlock(); + + if(block == Blocks.MOSSY_COBBLESTONE) { + floorCalls[i++] = Dungeon.Data.MOSSY_COBBLESTONE_CALL; + } else if(block == Blocks.COBBLESTONE) { + floorCalls[i++] = Dungeon.Data.COBBLESTONE_CALL; + } else { + return null; + } + } + } + + return floorCalls; + } + + + @Override + public boolean isValidDimension(DimensionType dimension) { + return this.isOverworld(dimension); + } + + public static List create(World world, ChunkPos chunkPos) { + List finders = new ArrayList<>(); + finders.add(new DungeonFinder(world, chunkPos)); + + finders.add(new DungeonFinder(world, new ChunkPos(chunkPos.x - 1, chunkPos.z))); + finders.add(new DungeonFinder(world, new ChunkPos(chunkPos.x, chunkPos.z - 1))); + finders.add(new DungeonFinder(world, new ChunkPos(chunkPos.x - 1, chunkPos.z - 1))); + + finders.add(new DungeonFinder(world, new ChunkPos(chunkPos.x + 1, chunkPos.z))); + finders.add(new DungeonFinder(world, new ChunkPos(chunkPos.x, chunkPos.z + 1))); + finders.add(new DungeonFinder(world, new ChunkPos(chunkPos.x + 1, chunkPos.z + 1))); + + finders.add(new DungeonFinder(world, new ChunkPos(chunkPos.x - 1, chunkPos.z - 1))); + finders.add(new DungeonFinder(world, new ChunkPos(chunkPos.x - 1, chunkPos.z + 1))); + return finders; + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/finder/decorator/EndGatewayFinder.java b/src/main/java/kaptainwutax/seedcracker/finder/decorator/EndGatewayFinder.java new file mode 100644 index 00000000..1c322bea --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/finder/decorator/EndGatewayFinder.java @@ -0,0 +1,88 @@ +package kaptainwutax.seedcracker.finder.decorator; + +import kaptainwutax.featureutils.decorator.EndGateway; +import kaptainwutax.seedcracker.Features; +import kaptainwutax.seedcracker.SeedCracker; +import kaptainwutax.seedcracker.cracker.DataAddedEvent; +import kaptainwutax.seedcracker.finder.BlockFinder; +import kaptainwutax.seedcracker.finder.Finder; +import kaptainwutax.seedcracker.render.Color; +import kaptainwutax.seedcracker.render.Cuboid; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.World; +import net.minecraft.world.biome.Biome; +import net.minecraft.world.dimension.DimensionType; + +import java.util.ArrayList; +import java.util.List; + +public class EndGatewayFinder extends BlockFinder { + + protected static List SEARCH_POSITIONS = buildSearchPositions(CHUNK_POSITIONS, pos -> { + return false; + }); + + public EndGatewayFinder(World world, ChunkPos chunkPos) { + super(world, chunkPos, Blocks.END_GATEWAY); + this.searchPositions = SEARCH_POSITIONS; + } + + @Override + public List findInChunk() { + Biome biome = this.world.getBiomeForNoiseGen((this.chunkPos.x << 2) + 2, 0, (this.chunkPos.z << 2) + 2); + + List result = super.findInChunk(); + List newResult = new ArrayList<>(); + + result.forEach(pos -> { + int height = this.findHeight(pos); + + if(height >= 3 && height <= 9) { + newResult.add(pos); + + EndGateway.Data data = Features.END_GATEWAY.at(pos.getX(), pos.getZ(), height); + + if(SeedCracker.get().getDataStorage().addBaseData(data, DataAddedEvent.POKE_STRUCTURES)) { + this.renderers.add(new Cuboid(pos.add(-1, -2, -1), pos.add(2, 3, 2), new Color(102, 102, 210))); + } + } + }); + + return newResult; + } + + private int findHeight(BlockPos pos) { + int height = 0; + + while(pos.getY() >= 0) { + pos = pos.down(); + height++; + + BlockState state = this.world.getBlockState(pos); + + //Bedrock generates below gateways. + if(state.getBlock() == Blocks.BEDROCK || state.getBlock() != Blocks.END_STONE) { + continue; + } + + break; + } + + return height - 1; + } + + @Override + public boolean isValidDimension(DimensionType dimension) { + return this.isEnd(dimension); + } + + public static List create(World world, ChunkPos chunkPos) { + List finders = new ArrayList<>(); + finders.add(new EndGatewayFinder(world, chunkPos)); + return finders; + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/finder/decorator/EndPillarsFinder.java b/src/main/java/kaptainwutax/seedcracker/finder/decorator/EndPillarsFinder.java new file mode 100644 index 00000000..276fbda7 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/finder/decorator/EndPillarsFinder.java @@ -0,0 +1,97 @@ +package kaptainwutax.seedcracker.finder.decorator; + +import kaptainwutax.seedcracker.SeedCracker; +import kaptainwutax.seedcracker.cracker.DataAddedEvent; +import kaptainwutax.seedcracker.cracker.PillarData; +import kaptainwutax.seedcracker.finder.BlockFinder; +import kaptainwutax.seedcracker.finder.Finder; +import kaptainwutax.seedcracker.render.Color; +import kaptainwutax.seedcracker.render.Cube; +import net.minecraft.block.Blocks; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3i; +import net.minecraft.world.World; +import net.minecraft.world.dimension.DimensionType; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class EndPillarsFinder extends Finder { + + private boolean alreadyFound; + protected BedrockMarkerFinder[] bedrockMarkers = new BedrockMarkerFinder[10]; + + public EndPillarsFinder(World world, ChunkPos chunkPos) { + super(world, chunkPos); + + this.alreadyFound = !SeedCracker.get().getDataStorage().addPillarData(null, DataAddedEvent.POKE_PILLARS); + if(this.alreadyFound)return; + + for(int i = 0; i < this.bedrockMarkers.length; i++) { + int x = MathHelper.floor(42.0D * Math.cos(2.0D * (-Math.PI + (Math.PI / 10.0D) * (double)i))); + int z = MathHelper.floor(42.0D * Math.sin(2.0D * (-Math.PI + (Math.PI / 10.0D) * (double)i))); + this.bedrockMarkers[i] = new BedrockMarkerFinder(this.world, new ChunkPos(new BlockPos(x, 0, z)), new BlockPos(x, 0, z)); + } + } + + @Override + public List findInChunk() { + List result = new ArrayList<>(); + + for(BedrockMarkerFinder bedrockMarker: this.bedrockMarkers) { + if(bedrockMarker == null)continue; + result.addAll(bedrockMarker.findInChunk()); + } + + if(result.size() == this.bedrockMarkers.length) { + PillarData pillarData = new PillarData(result.stream().map(Vec3i::getY).collect(Collectors.toList())); + + if(SeedCracker.get().getDataStorage().addPillarData(pillarData, DataAddedEvent.POKE_PILLARS)) { + result.forEach(pos -> this.renderers.add(new Cube(pos, new Color(128, 0, 128)))); + } + + } + + return result; + } + + @Override + public boolean isValidDimension(DimensionType dimension) { + return this.isEnd(dimension); + } + + public static List create(World world, ChunkPos chunkPos) { + List finders = new ArrayList<>(); + finders.add(new EndPillarsFinder(world, chunkPos)); + return finders; + } + + public static class BedrockMarkerFinder extends BlockFinder { + + protected static List SEARCH_POSITIONS = buildSearchPositions(CHUNK_POSITIONS, pos -> { + if(pos.getY() < 76)return true; + if(pos.getY() > 76 + 3 * 10)return true; + return false; + }); + + public BedrockMarkerFinder(World world, ChunkPos chunkPos, BlockPos xz) { + super(world, chunkPos, Blocks.BEDROCK); + this.searchPositions = SEARCH_POSITIONS; + } + + @Override + public List findInChunk() { + return super.findInChunk(); + } + + @Override + public boolean isValidDimension(DimensionType dimension) { + return true; + } + + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/finder/decorator/OreFinder.java b/src/main/java/kaptainwutax/seedcracker/finder/decorator/OreFinder.java new file mode 100644 index 00000000..5fff85b5 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/finder/decorator/OreFinder.java @@ -0,0 +1,206 @@ +package kaptainwutax.seedcracker.finder.decorator; + +import kaptainwutax.seedcracker.finder.BlockFinder; +import kaptainwutax.seedcracker.util.PosIterator; +import net.minecraft.block.BlockState; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.World; +import net.minecraft.world.dimension.DimensionType; +import net.minecraft.world.gen.feature.OreFeatureConfig; + +import java.util.*; +import java.util.stream.Collectors; + +public abstract class OreFinder extends BlockFinder { + + public static final int LOWEST = -1; + public static final int HIGHEST = 1; + + protected OreFeatureConfig oreFeatureConfig; + + public OreFinder(World world, ChunkPos chunkPos, OreFeatureConfig oreFeatureConfig) { + super(world, chunkPos, oreFeatureConfig.state); + this.oreFeatureConfig = oreFeatureConfig; + } + + @Override + public final List findInChunk() { + List result = super.findInChunk(); + List> veins = new ArrayList<>(); + + while(!result.isEmpty()) { + Set vein = this.buildVeinRecursively(result.get(0), new HashSet<>()); + vein.forEach(result::remove); + veins.add(vein); + } + + veins.removeIf(vein -> vein.size() > this.oreFeatureConfig.size); + veins.removeIf(vein -> !this.isCompleteVein(vein)); + + this.findOreVeins(veins); + return result; + } + + public Set buildVeinRecursively(BlockPos start, Set progress) { + progress.add(start); + + PosIterator.create(new BlockPos(-1, -1, -1), new BlockPos(1, 1, 1)).forEach(offset -> { + BlockPos pos = start.add(offset); + BlockState state = this.world.getBlockState(pos); + + if(!progress.contains(pos) && state.equals(this.oreFeatureConfig.state)) { + this.buildVeinRecursively(pos, progress); + } + }); + + return progress; + } + + private boolean isCompleteVein(Set vein) { + for(BlockPos pos: vein) { + for(Direction direction: Direction.values()) { + BlockState state = this.world.getBlockState(pos.offset(direction)); + if(!state.equals(this.oreFeatureConfig.state) && + !this.oreFeatureConfig.target.test(state, null))return false; + } + } + + return true; + } + + public boolean canVeinTo(BlockPos b1, BlockPos b2) { + return Math.max(Math.max(Math.abs(b1.getX() - b2.getX()), Math.abs(b1.getY() - b2.getY())), Math.abs(b1.getZ() - b2.getZ())) <= 1; + } + + public int findX(Set vein, int type) { + return getInt(vein.stream().map(BlockPos::getX).collect(Collectors.toList()), type); + } + + public int findY(Set vein, int type) { + return getInt(vein.stream().map(BlockPos::getY).collect(Collectors.toList()), type); + } + + public int findZ(Set vein, int type) { + return getInt(vein.stream().map(BlockPos::getZ).collect(Collectors.toList()), type); + } + + private int getInt(List ints, int type) { + Collections.sort(ints); + return ints.get(type == HIGHEST ? ints.size() - 1 : 0); + } + + public abstract void findOreVeins(List> veins); + + @Override + public boolean isValidDimension(DimensionType dimension) { + return this.isOverworld(dimension); + } + + /* + public boolean method_13628(float piRand, int nextIntA, int nextIntB, BlockPos blockPos_1, OreFeatureConfig oreFeatureConfig_1) { + float float_2 = (float)oreFeatureConfig_1.size / 8.0F; + int int_1 = MathHelper.ceil(((float)oreFeatureConfig_1.size / 16.0F * 2.0F + 1.0F) / 2.0F); + double double_1 = (float)blockPos_1.getX() + MathHelper.sin(piRand) * float_2; + double double_2 = (float)blockPos_1.getX() - MathHelper.sin(piRand) * float_2; + double double_3 = (float)blockPos_1.getZ() + MathHelper.cos(piRand) * float_2; + double double_4 = (float)blockPos_1.getZ() - MathHelper.cos(piRand) * float_2; + double double_5 = blockPos_1.getY() + nextIntA - 2; + double double_6 = blockPos_1.getY() + nextIntB - 2; + int int_3 = blockPos_1.getX() - MathHelper.ceil(float_2) - int_1; + int int_4 = blockPos_1.getY() - 2 - int_1; + int int_5 = blockPos_1.getZ() - MathHelper.ceil(float_2) - int_1; + int int_6 = 2 * (MathHelper.ceil(float_2) + int_1); + int int_7 = 2 * (2 + int_1); + + for(int int_8 = int_3; int_8 <= int_3 + int_6; ++int_8) { + for(int int_9 = int_5; int_9 <= int_5 + int_6; ++int_9) { + return this.generateVeinPart(oreFeatureConfig_1, double_1, double_2, double_3, double_4, double_5, double_6, int_3, int_4, int_5, int_6, int_7); + } + } + + return false; + } + + protected boolean generateVeinPart(OreFeatureConfig oreConfig, double double_1, double double_2, double double_3, double double_4, double double_5, double double_6, int int_1, int int_2, int int_3, int int_4, int int_5) { + int int_6 = 0; + BitSet bitSet_1 = new BitSet(int_4 * int_5 * int_4); + BlockPos.Mutable blockPos$Mutable_1 = new BlockPos.Mutable(); + double[] oreData = new double[oreConfig.size * 4]; + + for(int genStep = 0; genStep < oreConfig.size; ++genStep) { + float genStepProgress = (float)genStep / (float)oreConfig.size; + double xOffset = double_1 + (double_2 - double_1) * genStepProgress; + double yOffset = double_5 + (double_6 - double_5) * genStepProgress; + double zOffset = double_3 + (double_4 - double_3) * genStepProgress; + double genSizeMultiplier = random_1.nextDouble() * (double)oreConfig.size / 16.0D; + double randomBoundOffset = ((double)(MathHelper.sin((float)(Math.PI * genStepProgress)) + 1.0F) * genSizeMultiplier + 1.0D) / 2.0D; + oreData[genStep * 4 + 0] = xOffset; + oreData[genStep * 4 + 1] = yOffset; + oreData[genStep * 4 + 2] = zOffset; + oreData[genStep * 4 + 3] = randomBoundOffset; + } + + for(int genStep = 0; genStep < oreConfig.size - 1; ++genStep) { + if (oreData[genStep * 4 + 3] > 0.0D) { + for(int int_9 = genStep + 1; int_9 < oreConfig.size; ++int_9) { + if (oreData[int_9 * 4 + 3] > 0.0D) { + double xOffset = oreData[genStep * 4 + 0] - oreData[int_9 * 4 + 0]; + double yOffset = oreData[genStep * 4 + 1] - oreData[int_9 * 4 + 1]; + double zOffset = oreData[genStep * 4 + 2] - oreData[int_9 * 4 + 2]; + double genSizeMultiplier = oreData[genStep * 4 + 3] - oreData[int_9 * 4 + 3]; + if (genSizeMultiplier * genSizeMultiplier > xOffset * xOffset + yOffset * yOffset + zOffset * zOffset) { + if (genSizeMultiplier > 0.0D) { + oreData[int_9 * 4 + 3] = -1.0D; + } else { + oreData[genStep * 4 + 3] = -1.0D; + } + } + } + } + } + } + + for(int genStep = 0; genStep < oreConfig.size; ++genStep) { + double randomBoundOffset = oreData[genStep * 4 + 3]; + if (randomBoundOffset < 0.0D)continue; + + double xOffset = oreData[genStep * 4 + 0]; + double yOffset = oreData[genStep * 4 + 1]; + double zOffset = oreData[genStep * 4 + 2]; + int lowerXbound = Math.max(MathHelper.floor(xOffset - randomBoundOffset), int_1); + int lowerYbound = Math.max(MathHelper.floor(yOffset - randomBoundOffset), int_2); + int lowerZbound = Math.max(MathHelper.floor(zOffset - randomBoundOffset), int_3); + int upperXbound = Math.max(MathHelper.floor(xOffset + randomBoundOffset), lowerXbound); + int upperYbound = Math.max(MathHelper.floor(yOffset + randomBoundOffset), lowerYbound); + int upperZbound = Math.max(MathHelper.floor(zOffset + randomBoundOffset), lowerZbound); + + for(int attemptedPosX = lowerXbound; attemptedPosX <= upperXbound; ++attemptedPosX) { + for(int attemptedPosY = lowerYbound; attemptedPosY <= upperYbound; ++attemptedPosY) { + for(int attemptedPosZ = lowerZbound; attemptedPosZ <= upperZbound; ++attemptedPosZ) { + double posX = ((double) attemptedPosX + 0.5D - xOffset) / randomBoundOffset; + double posY = ((double) attemptedPosY + 0.5D - yOffset) / randomBoundOffset; + double posZ = ((double) attemptedPosZ + 0.5D - zOffset) / randomBoundOffset; + boolean posInUnitSphere = posX * posX + posY * posY + posZ * posZ < 1.0D; + + if(posInUnitSphere) { + int int_20 = attemptedPosX - int_1 + (attemptedPosY - int_2) * int_4 + (attemptedPosZ - int_3) * int_4 * int_5; + if (!bitSet_1.get(int_20)) { + bitSet_1.set(int_20); + blockPos$Mutable_1.set(attemptedPosX, attemptedPosY, attemptedPosZ); + if (oreConfig.target.getCondition().test(iWorld_1.getBlockState(blockPos$Mutable_1))) { + iWorld_1.setBlockState(blockPos$Mutable_1, oreConfig.state, 2); + ++int_6; + } + } + } + } + } + } + } + + return int_6 > 0; + }*/ + +} diff --git a/src/main/java/kaptainwutax/seedcracker/finder/decorator/ore/EmeraldOreFinder.java b/src/main/java/kaptainwutax/seedcracker/finder/decorator/ore/EmeraldOreFinder.java new file mode 100644 index 00000000..6b819958 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/finder/decorator/ore/EmeraldOreFinder.java @@ -0,0 +1,64 @@ +package kaptainwutax.seedcracker.finder.decorator.ore; + +import kaptainwutax.seedcracker.Features; +import kaptainwutax.seedcracker.SeedCracker; +import kaptainwutax.seedcracker.cracker.DataAddedEvent; +import kaptainwutax.seedcracker.cracker.decorator.EmeraldOre; +import kaptainwutax.seedcracker.finder.BlockFinder; +import kaptainwutax.seedcracker.finder.Finder; +import kaptainwutax.seedcracker.render.Color; +import kaptainwutax.seedcracker.render.Cube; +import kaptainwutax.seedcracker.util.BiomeFixer; +import net.minecraft.block.Blocks; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.World; +import net.minecraft.world.biome.Biome; +import net.minecraft.world.dimension.DimensionType; + +import java.util.ArrayList; +import java.util.List; + +public class EmeraldOreFinder extends BlockFinder { + + protected static List SEARCH_POSITIONS = Finder.buildSearchPositions(Finder.CHUNK_POSITIONS, pos -> { + if(pos.getY() < 4)return true; + if(pos.getY() > 28 + 4)return true; + return false; + }); + + public EmeraldOreFinder(World world, ChunkPos chunkPos) { + super(world, chunkPos, Blocks.EMERALD_ORE); + this.searchPositions = SEARCH_POSITIONS; + } + + @Override + public List findInChunk() { + Biome biome = this.world.getBiomeForNoiseGen((this.chunkPos.x << 2) + 2, 0, (this.chunkPos.z << 2) + 2); + + List result = super.findInChunk(); + if(result.isEmpty())return result; + + BlockPos pos = result.get(0); + + EmeraldOre.Data data = Features.EMERALD_ORE.at(pos.getX(), pos.getY(), pos.getZ(), BiomeFixer.swap(biome)); + + if(SeedCracker.get().getDataStorage().addBaseData(data, DataAddedEvent.POKE_STRUCTURES)) { + this.renderers.add(new Cube(pos, new Color(0, 255, 0))); + } + + return result; + } + + @Override + public boolean isValidDimension(DimensionType dimension) { + return this.isOverworld(dimension); + } + + public static List create(World world, ChunkPos chunkPos) { + List finders = new ArrayList<>(); + finders.add(new EmeraldOreFinder(world, chunkPos)); + return finders; + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/finder/structure/AbstractTempleFinder.java b/src/main/java/kaptainwutax/seedcracker/finder/structure/AbstractTempleFinder.java new file mode 100644 index 00000000..cb74f551 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/finder/structure/AbstractTempleFinder.java @@ -0,0 +1,82 @@ +package kaptainwutax.seedcracker.finder.structure; + +import kaptainwutax.seedcracker.finder.Finder; +import kaptainwutax.seedcracker.render.Color; +import kaptainwutax.seedcracker.render.Cube; +import kaptainwutax.seedcracker.render.Cuboid; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3i; +import net.minecraft.world.World; +import net.minecraft.world.biome.Biome; +import net.minecraft.world.dimension.DimensionType; +import net.minecraft.world.gen.feature.StructureFeature; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public abstract class AbstractTempleFinder extends Finder { + + protected static List SEARCH_POSITIONS = buildSearchPositions(CHUNK_POSITIONS, pos -> { + if(pos.getX() != 0)return true; + if(pos.getY() < 63)return true; + if(pos.getZ() != 0)return true; + return false; + }); + + protected List finders = new ArrayList<>(); + protected final Vec3i size; + + public AbstractTempleFinder(World world, ChunkPos chunkPos, Vec3i size) { + super(world, chunkPos); + + Direction.Type.HORIZONTAL.forEach(direction -> { + PieceFinder finder = new PieceFinder(world, chunkPos, direction, size); + + finder.searchPositions = SEARCH_POSITIONS; + + buildStructure(finder); + this.finders.add(finder); + }); + + this.size = size; + } + + public List findInChunkPiece(PieceFinder pieceFinder) { + Biome biome = this.world.getBiomeForNoiseGen((this.chunkPos.x << 2) + 2, 0, (this.chunkPos.z << 2) + 2); + + if(!biome.getGenerationSettings().hasStructureFeature(this.getStructureFeature())) { + return new ArrayList<>(); + } + + return pieceFinder.findInChunk(); + } + + protected abstract StructureFeature getStructureFeature(); + + public void addRenderers(PieceFinder pieceFinder, BlockPos origin, Color color) { + this.renderers.add(new Cuboid(origin, pieceFinder.getLayout(), color)); + BlockPos chunkStart = new BlockPos(origin.getX() & -16, origin.getY(), origin.getZ() & -16); + this.renderers.add(new Cube(chunkStart, color)); + } + + public Map> findInChunkPieces() { + Map> result = new HashMap<>(); + + this.finders.forEach(pieceFinder -> { + result.put(pieceFinder, this.findInChunkPiece(pieceFinder)); + }); + + return result; + } + + public abstract void buildStructure(PieceFinder finder); + + @Override + public boolean isValidDimension(DimensionType dimension) { + return this.isOverworld(dimension); + } +} diff --git a/src/main/java/kaptainwutax/seedcracker/finder/structure/BuriedTreasureFinder.java b/src/main/java/kaptainwutax/seedcracker/finder/structure/BuriedTreasureFinder.java new file mode 100644 index 00000000..f0fe9a8f --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/finder/structure/BuriedTreasureFinder.java @@ -0,0 +1,97 @@ +package kaptainwutax.seedcracker.finder.structure; + +import kaptainwutax.featureutils.structure.RegionStructure; +import kaptainwutax.seedcracker.Features; +import kaptainwutax.seedcracker.SeedCracker; +import kaptainwutax.seedcracker.cracker.DataAddedEvent; +import kaptainwutax.seedcracker.finder.BlockFinder; +import kaptainwutax.seedcracker.finder.Finder; +import kaptainwutax.seedcracker.render.Color; +import kaptainwutax.seedcracker.render.Cube; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.block.ChestBlock; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.World; +import net.minecraft.world.biome.Biome; +import net.minecraft.world.dimension.DimensionType; +import net.minecraft.world.gen.feature.StructureFeature; + +import java.util.ArrayList; +import java.util.List; + +public class BuriedTreasureFinder extends BlockFinder { + + protected static List SEARCH_POSITIONS = buildSearchPositions(CHUNK_POSITIONS, pos -> { + //Buried treasure chests always generate at (9, 9) within a chunk. + int localX = pos.getX() & 15; + int localZ = pos.getZ() & 15; + if(localX != 9 || localZ != 9)return true; + if(pos.getY() > 90)return true; + return false; + }); + + protected static final List CHEST_HOLDERS = new ArrayList<>(); + + static { + CHEST_HOLDERS.add(Blocks.SANDSTONE.getDefaultState()); + CHEST_HOLDERS.add(Blocks.STONE.getDefaultState()); + CHEST_HOLDERS.add(Blocks.ANDESITE.getDefaultState()); + CHEST_HOLDERS.add(Blocks.GRANITE.getDefaultState()); + CHEST_HOLDERS.add(Blocks.DIORITE.getDefaultState()); + + //Population can turn stone, andesite, granite and diorite into ores... + CHEST_HOLDERS.add(Blocks.COAL_ORE.getDefaultState()); + CHEST_HOLDERS.add(Blocks.IRON_ORE.getDefaultState()); + CHEST_HOLDERS.add(Blocks.GOLD_ORE.getDefaultState()); + + //Ocean can turn stone into gravel. + CHEST_HOLDERS.add(Blocks.GRAVEL.getDefaultState()); + } + + public BuriedTreasureFinder(World world, ChunkPos chunkPos) { + super(world, chunkPos, Blocks.CHEST); + this.searchPositions = SEARCH_POSITIONS; + } + + @Override + public List findInChunk() { + Biome biome = this.world.getBiomeForNoiseGen((this.chunkPos.x << 2) + 2, 0, (this.chunkPos.z << 2) + 2); + if(!biome.getGenerationSettings().hasStructureFeature(StructureFeature.BURIED_TREASURE))return new ArrayList<>(); + + List result = super.findInChunk(); + + result.removeIf(pos -> { + BlockState chest = world.getBlockState(pos); + if(chest.get(ChestBlock.WATERLOGGED))return true; + + BlockState chestHolder = world.getBlockState(pos.down()); + if(!CHEST_HOLDERS.contains(chestHolder))return true; + + return false; + }); + + result.forEach(pos -> { + RegionStructure.Data data = Features.BURIED_TREASURE.at(this.chunkPos.x, this.chunkPos.z); + + if(SeedCracker.get().getDataStorage().addBaseData(data, DataAddedEvent.POKE_STRUCTURES)) { + this.renderers.add(new Cube(pos, new Color(255, 255, 0))); + } + }); + + return result; + } + + @Override + public boolean isValidDimension(DimensionType dimension) { + return this.isOverworld(dimension); + } + + public static List create(World world, ChunkPos chunkPos) { + List finders = new ArrayList<>(); + finders.add(new BuriedTreasureFinder(world, chunkPos)); + return finders; + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/finder/structure/DesertPyramidFinder.java b/src/main/java/kaptainwutax/seedcracker/finder/structure/DesertPyramidFinder.java new file mode 100644 index 00000000..f4f845ae --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/finder/structure/DesertPyramidFinder.java @@ -0,0 +1,232 @@ +package kaptainwutax.seedcracker.finder.structure; + +import kaptainwutax.featureutils.structure.RegionStructure; +import kaptainwutax.seedcracker.Features; +import kaptainwutax.seedcracker.SeedCracker; +import kaptainwutax.seedcracker.cracker.DataAddedEvent; +import kaptainwutax.seedcracker.finder.Finder; +import kaptainwutax.seedcracker.render.Color; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.block.StairsBlock; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3i; +import net.minecraft.world.World; +import net.minecraft.world.gen.feature.StructureFeature; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class DesertPyramidFinder extends AbstractTempleFinder { + + public DesertPyramidFinder(World world, ChunkPos chunkPos) { + super(world, chunkPos, new Vec3i(21, 15, 21)); + } + + @Override + public List findInChunk() { + Map> result = super.findInChunkPieces(); + List combinedResult = new ArrayList<>(); + + result.forEach((pieceFinder, positions) -> { + combinedResult.addAll(positions); + + positions.forEach(pos -> { + RegionStructure.Data data = Features.DESERT_PYRAMID.at(this.chunkPos.x, this.chunkPos.z); + + if(SeedCracker.get().getDataStorage().addBaseData(data, DataAddedEvent.POKE_STRUCTURES)) { + this.addRenderers(pieceFinder, pos, new Color(255, 0, 255)); + } + }); + }); + + return combinedResult; + } + + @Override + protected StructureFeature getStructureFeature() { + return StructureFeature.DESERT_PYRAMID; + } + + @Override + public void buildStructure(PieceFinder finder) { + BlockState blockState_1 = Blocks.SANDSTONE_STAIRS.getDefaultState().with(StairsBlock.FACING, Direction.NORTH); + BlockState blockState_2 = Blocks.SANDSTONE_STAIRS.getDefaultState().with(StairsBlock.FACING, Direction.SOUTH); + BlockState blockState_3 = Blocks.SANDSTONE_STAIRS.getDefaultState().with(StairsBlock.FACING, Direction.EAST); + BlockState blockState_4 = Blocks.SANDSTONE_STAIRS.getDefaultState().with(StairsBlock.FACING, Direction.WEST); + finder.fillWithOutline(0, 0, 0, 4, 9, 4, Blocks.SANDSTONE.getDefaultState(), Blocks.AIR.getDefaultState(), false); + finder.fillWithOutline(1, 10, 1, 3, 10, 3, Blocks.SANDSTONE.getDefaultState(), Blocks.SANDSTONE.getDefaultState(), false); + finder.addBlock(blockState_1, 2, 10, 0); + finder.addBlock(blockState_2, 2, 10, 4); + finder.addBlock(blockState_3, 0, 10, 2); + finder.addBlock(blockState_4, 4, 10, 2); + finder.fillWithOutline(finder.width - 5, 0, 0, finder.width - 1, 9, 4, Blocks.SANDSTONE.getDefaultState(), Blocks.AIR.getDefaultState(), false); + finder.fillWithOutline(finder.width - 4, 10, 1, finder.width - 2, 10, 3, Blocks.SANDSTONE.getDefaultState(), Blocks.SANDSTONE.getDefaultState(), false); + finder.addBlock(blockState_1, finder.width - 3, 10, 0); + finder.addBlock(blockState_2, finder.width - 3, 10, 4); + finder.addBlock(blockState_3, finder.width - 5, 10, 2); + finder.addBlock(blockState_4, finder.width - 1, 10, 2); + finder.fillWithOutline(8, 0, 0, 12, 4, 4, Blocks.SANDSTONE.getDefaultState(), Blocks.AIR.getDefaultState(), false); + finder.fillWithOutline(9, 1, 0, 11, 3, 4, Blocks.AIR.getDefaultState(), Blocks.AIR.getDefaultState(), false); + finder.addBlock(Blocks.CUT_SANDSTONE.getDefaultState(), 9, 1, 1); + finder.addBlock(Blocks.CUT_SANDSTONE.getDefaultState(), 9, 2, 1); + finder.addBlock(Blocks.CUT_SANDSTONE.getDefaultState(), 9, 3, 1); + finder.addBlock(Blocks.CUT_SANDSTONE.getDefaultState(), 10, 3, 1); + finder.addBlock(Blocks.CUT_SANDSTONE.getDefaultState(), 11, 3, 1); + finder.addBlock(Blocks.CUT_SANDSTONE.getDefaultState(), 11, 2, 1); + finder.addBlock(Blocks.CUT_SANDSTONE.getDefaultState(), 11, 1, 1); + finder.fillWithOutline(4, 1, 1, 8, 3, 3, Blocks.SANDSTONE.getDefaultState(), Blocks.AIR.getDefaultState(), false); + finder.fillWithOutline(4, 1, 2, 8, 2, 2, Blocks.AIR.getDefaultState(), Blocks.AIR.getDefaultState(), false); + finder.fillWithOutline(12, 1, 1, 16, 3, 3, Blocks.SANDSTONE.getDefaultState(), Blocks.AIR.getDefaultState(), false); + finder.fillWithOutline(12, 1, 2, 16, 2, 2, Blocks.AIR.getDefaultState(), Blocks.AIR.getDefaultState(), false); + finder.fillWithOutline(5, 4, 5, finder.width - 6, 4, finder.depth - 6, Blocks.SANDSTONE.getDefaultState(), Blocks.SANDSTONE.getDefaultState(), false); + finder.fillWithOutline(9, 4, 9, 11, 4, 11, Blocks.AIR.getDefaultState(), Blocks.AIR.getDefaultState(), false); + finder.fillWithOutline(8, 1, 8, 8, 3, 8, Blocks.CUT_SANDSTONE.getDefaultState(), Blocks.CUT_SANDSTONE.getDefaultState(), false); + finder.fillWithOutline(12, 1, 8, 12, 3, 8, Blocks.CUT_SANDSTONE.getDefaultState(), Blocks.CUT_SANDSTONE.getDefaultState(), false); + finder.fillWithOutline(8, 1, 12, 8, 3, 12, Blocks.CUT_SANDSTONE.getDefaultState(), Blocks.CUT_SANDSTONE.getDefaultState(), false); + finder.fillWithOutline(12, 1, 12, 12, 3, 12, Blocks.CUT_SANDSTONE.getDefaultState(), Blocks.CUT_SANDSTONE.getDefaultState(), false); + finder.fillWithOutline(1, 1, 5, 4, 4, 11, Blocks.SANDSTONE.getDefaultState(), Blocks.SANDSTONE.getDefaultState(), false); + finder.fillWithOutline(finder.width - 5, 1, 5, finder.width - 2, 4, 11, Blocks.SANDSTONE.getDefaultState(), Blocks.SANDSTONE.getDefaultState(), false); + finder.fillWithOutline(6, 7, 9, 6, 7, 11, Blocks.SANDSTONE.getDefaultState(), Blocks.SANDSTONE.getDefaultState(), false); + finder.fillWithOutline(finder.width - 7, 7, 9, finder.width - 7, 7, 11, Blocks.SANDSTONE.getDefaultState(), Blocks.SANDSTONE.getDefaultState(), false); + finder.fillWithOutline(5, 5, 9, 5, 7, 11, Blocks.CUT_SANDSTONE.getDefaultState(), Blocks.CUT_SANDSTONE.getDefaultState(), false); + finder.fillWithOutline(finder.width - 6, 5, 9, finder.width - 6, 7, 11, Blocks.CUT_SANDSTONE.getDefaultState(), Blocks.CUT_SANDSTONE.getDefaultState(), false); + finder.addBlock(Blocks.AIR.getDefaultState(), 5, 5, 10); + finder.addBlock(Blocks.AIR.getDefaultState(), 5, 6, 10); + finder.addBlock(Blocks.AIR.getDefaultState(), 6, 6, 10); + finder.addBlock(Blocks.AIR.getDefaultState(), finder.width - 6, 5, 10); + finder.addBlock(Blocks.AIR.getDefaultState(), finder.width - 6, 6, 10); + finder.addBlock(Blocks.AIR.getDefaultState(), finder.width - 7, 6, 10); + finder.fillWithOutline(2, 4, 4, 2, 6, 4, Blocks.AIR.getDefaultState(), Blocks.AIR.getDefaultState(), false); + finder.fillWithOutline(finder.width - 3, 4, 4, finder.width - 3, 6, 4, Blocks.AIR.getDefaultState(), Blocks.AIR.getDefaultState(), false); + finder.addBlock(blockState_1, 2, 4, 5); + finder.addBlock(blockState_1, 2, 3, 4); + finder.addBlock(blockState_1, finder.width - 3, 4, 5); + finder.addBlock(blockState_1, finder.width - 3, 3, 4); + finder.fillWithOutline(1, 1, 3, 2, 2, 3, Blocks.SANDSTONE.getDefaultState(), Blocks.SANDSTONE.getDefaultState(), false); + finder.fillWithOutline(finder.width - 3, 1, 3, finder.width - 2, 2, 3, Blocks.SANDSTONE.getDefaultState(), Blocks.SANDSTONE.getDefaultState(), false); + finder.addBlock(Blocks.SANDSTONE.getDefaultState(), 1, 1, 2); + finder.addBlock(Blocks.SANDSTONE.getDefaultState(), finder.width - 2, 1, 2); + finder.addBlock(Blocks.SANDSTONE_SLAB.getDefaultState(), 1, 2, 2); + finder.addBlock(Blocks.SANDSTONE_SLAB.getDefaultState(), finder.width - 2, 2, 2); + finder.addBlock(blockState_4, 2, 1, 2); + finder.addBlock(blockState_3, finder.width - 3, 1, 2); + finder.fillWithOutline(4, 3, 5, 4, 3, 17, Blocks.SANDSTONE.getDefaultState(), Blocks.SANDSTONE.getDefaultState(), false); + finder.fillWithOutline(finder.width - 5, 3, 5, finder.width - 5, 3, 17, Blocks.SANDSTONE.getDefaultState(), Blocks.SANDSTONE.getDefaultState(), false); + finder.fillWithOutline(3, 1, 5, 4, 2, 16, Blocks.AIR.getDefaultState(), Blocks.AIR.getDefaultState(), false); + finder.fillWithOutline(finder.width - 6, 1, 5, finder.width - 5, 2, 16, Blocks.AIR.getDefaultState(), Blocks.AIR.getDefaultState(), false); + + int int_7; + for(int_7 = 5; int_7 <= 17; int_7 += 2) { + finder.addBlock(Blocks.CUT_SANDSTONE.getDefaultState(), 4, 1, int_7); + finder.addBlock(Blocks.CHISELED_SANDSTONE.getDefaultState(), 4, 2, int_7); + finder.addBlock(Blocks.CUT_SANDSTONE.getDefaultState(), finder.width - 5, 1, int_7); + finder.addBlock(Blocks.CHISELED_SANDSTONE.getDefaultState(), finder.width - 5, 2, int_7); + } + + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), 10, 0, 7); + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), 10, 0, 8); + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), 9, 0, 9); + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), 11, 0, 9); + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), 8, 0, 10); + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), 12, 0, 10); + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), 7, 0, 10); + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), 13, 0, 10); + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), 9, 0, 11); + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), 11, 0, 11); + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), 10, 0, 12); + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), 10, 0, 13); + finder.addBlock(Blocks.BLUE_TERRACOTTA.getDefaultState(), 10, 0, 10); + + for(int_7 = 0; int_7 <= finder.width - 1; int_7 += finder.width - 1) { + finder.addBlock(Blocks.CUT_SANDSTONE.getDefaultState(), int_7, 2, 1); + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), int_7, 2, 2); + finder.addBlock(Blocks.CUT_SANDSTONE.getDefaultState(), int_7, 2, 3); + finder.addBlock(Blocks.CUT_SANDSTONE.getDefaultState(), int_7, 3, 1); + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), int_7, 3, 2); + finder.addBlock(Blocks.CUT_SANDSTONE.getDefaultState(), int_7, 3, 3); + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), int_7, 4, 1); + finder.addBlock(Blocks.CHISELED_SANDSTONE.getDefaultState(), int_7, 4, 2); + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), int_7, 4, 3); + finder.addBlock(Blocks.CUT_SANDSTONE.getDefaultState(), int_7, 5, 1); + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), int_7, 5, 2); + finder.addBlock(Blocks.CUT_SANDSTONE.getDefaultState(), int_7, 5, 3); + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), int_7, 6, 1); + finder.addBlock(Blocks.CHISELED_SANDSTONE.getDefaultState(), int_7, 6, 2); + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), int_7, 6, 3); + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), int_7, 7, 1); + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), int_7, 7, 2); + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), int_7, 7, 3); + finder.addBlock(Blocks.CUT_SANDSTONE.getDefaultState(), int_7, 8, 1); + finder.addBlock(Blocks.CUT_SANDSTONE.getDefaultState(), int_7, 8, 2); + finder.addBlock(Blocks.CUT_SANDSTONE.getDefaultState(), int_7, 8, 3); + } + + for(int_7 = 2; int_7 <= finder.width - 3; int_7 += finder.width - 3 - 2) { + finder.addBlock(Blocks.CUT_SANDSTONE.getDefaultState(), int_7 - 1, 2, 0); + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), int_7, 2, 0); + finder.addBlock(Blocks.CUT_SANDSTONE.getDefaultState(), int_7 + 1, 2, 0); + finder.addBlock(Blocks.CUT_SANDSTONE.getDefaultState(), int_7 - 1, 3, 0); + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), int_7, 3, 0); + finder.addBlock(Blocks.CUT_SANDSTONE.getDefaultState(), int_7 + 1, 3, 0); + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), int_7 - 1, 4, 0); + finder.addBlock(Blocks.CHISELED_SANDSTONE.getDefaultState(), int_7, 4, 0); + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), int_7 + 1, 4, 0); + finder.addBlock(Blocks.CUT_SANDSTONE.getDefaultState(), int_7 - 1, 5, 0); + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), int_7, 5, 0); + finder.addBlock(Blocks.CUT_SANDSTONE.getDefaultState(), int_7 + 1, 5, 0); + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), int_7 - 1, 6, 0); + finder.addBlock(Blocks.CHISELED_SANDSTONE.getDefaultState(), int_7, 6, 0); + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), int_7 + 1, 6, 0); + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), int_7 - 1, 7, 0); + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), int_7, 7, 0); + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), int_7 + 1, 7, 0); + finder.addBlock(Blocks.CUT_SANDSTONE.getDefaultState(), int_7 - 1, 8, 0); + finder.addBlock(Blocks.CUT_SANDSTONE.getDefaultState(), int_7, 8, 0); + finder.addBlock(Blocks.CUT_SANDSTONE.getDefaultState(), int_7 + 1, 8, 0); + } + + finder.fillWithOutline(8, 4, 0, 12, 6, 0, Blocks.CUT_SANDSTONE.getDefaultState(), Blocks.CUT_SANDSTONE.getDefaultState(), false); + finder.addBlock(Blocks.AIR.getDefaultState(), 8, 6, 0); + finder.addBlock(Blocks.AIR.getDefaultState(), 12, 6, 0); + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), 9, 5, 0); + finder.addBlock(Blocks.CHISELED_SANDSTONE.getDefaultState(), 10, 5, 0); + finder.addBlock(Blocks.ORANGE_TERRACOTTA.getDefaultState(), 11, 5, 0); + finder.fillWithOutline(8, -14, 8, 12, -11, 12, Blocks.CUT_SANDSTONE.getDefaultState(), Blocks.CUT_SANDSTONE.getDefaultState(), false); + finder.fillWithOutline(8, -10, 8, 12, -10, 12, Blocks.CHISELED_SANDSTONE.getDefaultState(), Blocks.CHISELED_SANDSTONE.getDefaultState(), false); + finder.fillWithOutline(8, -9, 8, 12, -9, 12, Blocks.CUT_SANDSTONE.getDefaultState(), Blocks.CUT_SANDSTONE.getDefaultState(), false); + finder.fillWithOutline(8, -8, 8, 12, -1, 12, Blocks.SANDSTONE.getDefaultState(), Blocks.SANDSTONE.getDefaultState(), false); + finder.fillWithOutline(9, -11, 9, 11, -1, 11, Blocks.AIR.getDefaultState(), Blocks.AIR.getDefaultState(), false); + finder.addBlock(Blocks.STONE_PRESSURE_PLATE.getDefaultState(), 10, -11, 10); + finder.fillWithOutline(9, -13, 9, 11, -13, 11, Blocks.TNT.getDefaultState(), Blocks.AIR.getDefaultState(), false); + finder.addBlock(Blocks.AIR.getDefaultState(), 8, -11, 10); + finder.addBlock(Blocks.AIR.getDefaultState(), 8, -10, 10); + finder.addBlock(Blocks.CHISELED_SANDSTONE.getDefaultState(), 7, -10, 10); + finder.addBlock(Blocks.CUT_SANDSTONE.getDefaultState(), 7, -11, 10); + finder.addBlock(Blocks.AIR.getDefaultState(), 12, -11, 10); + finder.addBlock(Blocks.AIR.getDefaultState(), 12, -10, 10); + finder.addBlock(Blocks.CHISELED_SANDSTONE.getDefaultState(), 13, -10, 10); + finder.addBlock(Blocks.CUT_SANDSTONE.getDefaultState(), 13, -11, 10); + finder.addBlock(Blocks.AIR.getDefaultState(), 10, -11, 8); + finder.addBlock(Blocks.AIR.getDefaultState(), 10, -10, 8); + finder.addBlock(Blocks.CHISELED_SANDSTONE.getDefaultState(), 10, -10, 7); + finder.addBlock(Blocks.CUT_SANDSTONE.getDefaultState(), 10, -11, 7); + finder.addBlock(Blocks.AIR.getDefaultState(), 10, -11, 12); + finder.addBlock(Blocks.AIR.getDefaultState(), 10, -10, 12); + finder.addBlock(Blocks.CHISELED_SANDSTONE.getDefaultState(), 10, -10, 13); + finder.addBlock(Blocks.CUT_SANDSTONE.getDefaultState(), 10, -11, 13); + } + + public static List create(World world, ChunkPos chunkPos) { + List finders = new ArrayList<>(); + finders.add(new DesertPyramidFinder(world, chunkPos)); + finders.add(new DesertPyramidFinder(world, new ChunkPos(chunkPos.x - 1, chunkPos.z))); + finders.add(new DesertPyramidFinder(world, new ChunkPos(chunkPos.x, chunkPos.z - 1))); + finders.add(new DesertPyramidFinder(world, new ChunkPos(chunkPos.x - 1, chunkPos.z - 1))); + return finders; + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/finder/structure/EndCityFinder.java b/src/main/java/kaptainwutax/seedcracker/finder/structure/EndCityFinder.java new file mode 100644 index 00000000..9ec84ad4 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/finder/structure/EndCityFinder.java @@ -0,0 +1,135 @@ +package kaptainwutax.seedcracker.finder.structure; + +import kaptainwutax.featureutils.structure.RegionStructure; +import kaptainwutax.seedcracker.Features; +import kaptainwutax.seedcracker.SeedCracker; +import kaptainwutax.seedcracker.cracker.DataAddedEvent; +import kaptainwutax.seedcracker.finder.Finder; +import kaptainwutax.seedcracker.render.Color; +import kaptainwutax.seedcracker.render.Cube; +import kaptainwutax.seedcracker.render.Cuboid; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3i; +import net.minecraft.world.World; +import net.minecraft.world.biome.Biome; +import net.minecraft.world.dimension.DimensionType; +import net.minecraft.world.gen.feature.StructureFeature; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class EndCityFinder extends Finder { + + protected static List SEARCH_POSITIONS = buildSearchPositions(CHUNK_POSITIONS, pos -> { + if(pos.getY() > 90)return true; + return false; + }); + + protected List finders = new ArrayList<>(); + protected final Vec3i size = new Vec3i(8, 4, 8); + + public EndCityFinder(World world, ChunkPos chunkPos) { + super(world, chunkPos); + + Direction.Type.HORIZONTAL.forEach(direction -> { + PieceFinder finder = new PieceFinder(world, chunkPos, direction, size); + + finder.searchPositions = SEARCH_POSITIONS; + + buildStructure(finder); + this.finders.add(finder); + }); + } + + private void buildStructure(PieceFinder finder) { + BlockState air = Blocks.AIR.getDefaultState(); + BlockState endstoneBricks = Blocks.END_STONE_BRICKS.getDefaultState(); + BlockState purpur = Blocks.PURPUR_BLOCK.getDefaultState(); + BlockState purpurPillar = Blocks.PURPUR_PILLAR.getDefaultState(); + BlockState purpleGlass = Blocks.MAGENTA_STAINED_GLASS.getDefaultState(); + + //Walls + finder.fillWithOutline(0, 0, 0, 7, 4, 7, endstoneBricks, null, false); + + //Wall sides + finder.fillWithOutline(0, 0, 0, 0, 3, 0, purpurPillar, purpurPillar, false); + finder.fillWithOutline(7, 0, 0, 7, 3, 0, purpurPillar, purpurPillar, false); + finder.fillWithOutline(0, 0, 7, 0, 3, 7, purpurPillar, purpurPillar, false); + finder.fillWithOutline(7, 0, 7, 7, 3, 7, purpurPillar, purpurPillar, false); + + //Floor + finder.fillWithOutline(0, 0, 0, 7, 0, 7, purpur, purpur, false); + + //Doorway + finder.fillWithOutline(3, 1, 0, 4, 3, 0, air, air, false); + + //Windows + finder.fillWithOutline(0, 2, 2, 0, 3, 2, purpleGlass, purpleGlass, false); + finder.fillWithOutline(0, 2, 5, 0, 3, 5, purpleGlass, purpleGlass, false); + finder.fillWithOutline(7, 2, 2, 7, 3, 2, purpleGlass, purpleGlass, false); + finder.fillWithOutline(7, 2, 5, 7, 3, 5, purpleGlass, purpleGlass, false); + } + + @Override + public List findInChunk() { + Biome biome = this.world.getBiomeForNoiseGen((this.chunkPos.x << 2) + 2, 0, (this.chunkPos.z << 2) + 2); + + if(!biome.getGenerationSettings().hasStructureFeature(StructureFeature.END_CITY)) { + return new ArrayList<>(); + } + + Map> result = this.findInChunkPieces(); + List combinedResult = new ArrayList<>(); + + result.forEach((pieceFinder, positions) -> { + positions.removeIf(pos -> { + //Figure this out, it's not a trivial task. + return false; + }); + + combinedResult.addAll(positions); + + positions.forEach(pos -> { + RegionStructure.Data data = Features.END_CITY.at(this.chunkPos.x, this.chunkPos.z); + + if(SeedCracker.get().getDataStorage().addBaseData(data, DataAddedEvent.POKE_STRUCTURES)) { + this.renderers.add(new Cuboid(pos, pieceFinder.getLayout(), new Color(153, 0, 153))); + this.renderers.add(new Cube(pos, new Color(153, 0, 153))); + } + }); + }); + + return combinedResult; + } + + public Map> findInChunkPieces() { + Map> result = new HashMap<>(); + + this.finders.forEach(pieceFinder -> { + result.put(pieceFinder, pieceFinder.findInChunk()); + }); + + return result; + } + + @Override + public boolean isValidDimension(DimensionType dimension) { + return this.isEnd(dimension); + } + + public static List create(World world, ChunkPos chunkPos) { + List finders = new ArrayList<>(); + finders.add(new EndCityFinder(world, chunkPos)); + finders.add(new EndCityFinder(world, new ChunkPos(chunkPos.x - 1, chunkPos.z))); + finders.add(new EndCityFinder(world, new ChunkPos(chunkPos.x, chunkPos.z - 1))); + finders.add(new EndCityFinder(world, new ChunkPos(chunkPos.x - 1, chunkPos.z - 1))); + return finders; + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/finder/structure/IglooFinder.java b/src/main/java/kaptainwutax/seedcracker/finder/structure/IglooFinder.java new file mode 100644 index 00000000..6cb6bc98 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/finder/structure/IglooFinder.java @@ -0,0 +1,101 @@ +package kaptainwutax.seedcracker.finder.structure; + +import kaptainwutax.featureutils.structure.RegionStructure; +import kaptainwutax.seedcracker.Features; +import kaptainwutax.seedcracker.SeedCracker; +import kaptainwutax.seedcracker.cracker.DataAddedEvent; +import kaptainwutax.seedcracker.finder.Finder; +import kaptainwutax.seedcracker.render.Color; +import kaptainwutax.seedcracker.render.Cube; +import kaptainwutax.seedcracker.render.Cuboid; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.math.Vec3i; +import net.minecraft.world.World; +import net.minecraft.world.gen.feature.StructureFeature; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class IglooFinder extends AbstractTempleFinder { + + protected static List SEARCH_POSITIONS = buildSearchPositions(CHUNK_POSITIONS, pos -> { + return false; + }); + + public IglooFinder(World world, ChunkPos chunkPos) { + super(world, chunkPos, new Vec3i(7, 5, 8)); + + //Igloos are weird. + this.finders.forEach(finder -> { + finder.searchPositions = SEARCH_POSITIONS; + }); + } + + @Override + public List findInChunk() { + Map> result = this.findInChunkPieces(); + List combinedResult = new ArrayList<>(); + + result.forEach((pieceFinder, positions) -> { + combinedResult.addAll(positions); + + positions.forEach(pos -> { + RegionStructure.Data data = Features.IGLOO.at(this.chunkPos.x, this.chunkPos.z); + + if(SeedCracker.get().getDataStorage().addBaseData(data, DataAddedEvent.POKE_STRUCTURES)) { + this.renderers.add(new Cuboid(pos, pieceFinder.getLayout(), new Color(0, 255, 255))); + this.renderers.add(new Cube(pos, new Color(0, 255, 255))); + } + }); + }); + + return combinedResult; + } + + @Override + public void buildStructure(PieceFinder finder) { + BlockState air = Blocks.AIR.getDefaultState(); + BlockState snow = Blocks.SNOW_BLOCK.getDefaultState(); + BlockState ice = Blocks.ICE.getDefaultState(); + + for(int y = 0; y < 3; y++) { + finder.addBlock(snow, 2, y, 0); + finder.addBlock(snow, 2, y, 1); + finder.addBlock(snow, 1, y, 2); + finder.addBlock(snow, 0, y, 3); + finder.addBlock(snow, 0, y, 4); + finder.addBlock(ice, 0, 1, 4); + finder.addBlock(snow, 0, y, 5); + finder.addBlock(snow, 1, y, 6); + finder.addBlock(snow, 2, y, 7); + + finder.addBlock(snow, 3, y, 7); + + finder.addBlock(snow, 4, y, 0); + finder.addBlock(snow, 4, y, 1); + finder.addBlock(snow, 5, y, 2); + finder.addBlock(snow, 6, y, 3); + finder.addBlock(snow, 6, y, 4); + finder.addBlock(ice, 6, 1, 4); + finder.addBlock(snow, 6, y, 5); + finder.addBlock(snow, 5, y, 6); + finder.addBlock(snow, 4, y, 7); + } + } + + @Override + protected StructureFeature getStructureFeature() { + return StructureFeature.IGLOO; + } + + public static List create(World world, ChunkPos chunkPos) { + List finders = new ArrayList<>(); + finders.add(new IglooFinder(world, chunkPos)); + return finders; + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/finder/structure/JunglePyramidFinder.java b/src/main/java/kaptainwutax/seedcracker/finder/structure/JunglePyramidFinder.java new file mode 100644 index 00000000..9e4a6042 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/finder/structure/JunglePyramidFinder.java @@ -0,0 +1,138 @@ +package kaptainwutax.seedcracker.finder.structure; + +import kaptainwutax.featureutils.structure.RegionStructure; +import kaptainwutax.seedcracker.Features; +import kaptainwutax.seedcracker.SeedCracker; +import kaptainwutax.seedcracker.cracker.DataAddedEvent; +import kaptainwutax.seedcracker.finder.Finder; +import kaptainwutax.seedcracker.render.Color; +import net.minecraft.block.*; +import net.minecraft.block.enums.WallMountLocation; +import net.minecraft.block.enums.WireConnection; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3i; +import net.minecraft.world.World; +import net.minecraft.world.gen.feature.StructureFeature; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class JunglePyramidFinder extends AbstractTempleFinder { + + public JunglePyramidFinder(World world, ChunkPos chunkPos) { + super(world, chunkPos, new Vec3i(12, 10, 15)); + } + + @Override + public List findInChunk() { + Map> result = super.findInChunkPieces(); + List combinedResult = new ArrayList<>(); + + result.forEach((pieceFinder, positions) -> { + combinedResult.addAll(positions); + + positions.forEach(pos -> { + RegionStructure.Data data = Features.JUNGLE_PYRAMID.at(this.chunkPos.x, this.chunkPos.z); + + if(SeedCracker.get().getDataStorage().addBaseData(data, DataAddedEvent.POKE_STRUCTURES)) { + this.addRenderers(pieceFinder, pos, new Color(255, 0, 255)); + } + }); + }); + + return combinedResult; + } + + @Override + protected StructureFeature getStructureFeature() { + return StructureFeature.JUNGLE_PYRAMID; + } + + @Override + public void buildStructure(PieceFinder finder) { + BlockState eastStairs = Blocks.COBBLESTONE_STAIRS.getDefaultState().with(StairsBlock.FACING, Direction.EAST); + BlockState westStairs = Blocks.COBBLESTONE_STAIRS.getDefaultState().with(StairsBlock.FACING, Direction.WEST); + BlockState southStairs = Blocks.COBBLESTONE_STAIRS.getDefaultState().with(StairsBlock.FACING, Direction.SOUTH); + BlockState northStairs = Blocks.COBBLESTONE_STAIRS.getDefaultState().with(StairsBlock.FACING, Direction.NORTH); + finder.addBlock(northStairs, 5, 9, 6); + finder.addBlock(northStairs, 6, 9, 6); + finder.addBlock(southStairs, 5, 9, 8); + finder.addBlock(southStairs, 6, 9, 8); + finder.addBlock(northStairs, 4, 0, 0); + finder.addBlock(northStairs, 5, 0, 0); + finder.addBlock(northStairs, 6, 0, 0); + finder.addBlock(northStairs, 7, 0, 0); + finder.addBlock(northStairs, 4, 1, 8); + finder.addBlock(northStairs, 4, 2, 9); + finder.addBlock(northStairs, 4, 3, 10); + finder.addBlock(northStairs, 7, 1, 8); + finder.addBlock(northStairs, 7, 2, 9); + finder.addBlock(northStairs, 7, 3, 10); + finder.addBlock(eastStairs, 4, 4, 5); + finder.addBlock(westStairs, 7, 4, 5); + finder.addBlock((Blocks.TRIPWIRE_HOOK.getDefaultState().with(TripwireHookBlock.FACING, Direction.EAST)).with(TripwireHookBlock.ATTACHED, true), 1, -3, 8); + finder.addBlock((Blocks.TRIPWIRE_HOOK.getDefaultState().with(TripwireHookBlock.FACING, Direction.WEST)).with(TripwireHookBlock.ATTACHED, true), 4, -3, 8); + finder.addBlock(((Blocks.TRIPWIRE.getDefaultState().with(TripwireBlock.EAST, true)).with(TripwireBlock.WEST, true)).with(TripwireBlock.ATTACHED, true), 2, -3, 8); + finder.addBlock(((Blocks.TRIPWIRE.getDefaultState().with(TripwireBlock.EAST, true)).with(TripwireBlock.WEST, true)).with(TripwireBlock.ATTACHED, true), 3, -3, 8); + BlockState blockState_5 = (Blocks.REDSTONE_WIRE.getDefaultState().with(RedstoneWireBlock.WIRE_CONNECTION_NORTH, WireConnection.SIDE)).with(RedstoneWireBlock.WIRE_CONNECTION_SOUTH, WireConnection.SIDE); + finder.addBlock(Blocks.REDSTONE_WIRE.getDefaultState().with(RedstoneWireBlock.WIRE_CONNECTION_SOUTH, WireConnection.SIDE), 5, -3, 7); + finder.addBlock(blockState_5, 5, -3, 6); + finder.addBlock(blockState_5, 5, -3, 5); + finder.addBlock(blockState_5, 5, -3, 4); + finder.addBlock(blockState_5, 5, -3, 3); + finder.addBlock(blockState_5, 5, -3, 2); + finder.addBlock((Blocks.REDSTONE_WIRE.getDefaultState().with(RedstoneWireBlock.WIRE_CONNECTION_NORTH, WireConnection.SIDE)).with(RedstoneWireBlock.WIRE_CONNECTION_WEST, WireConnection.SIDE), 5, -3, 1); + finder.addBlock(Blocks.REDSTONE_WIRE.getDefaultState().with(RedstoneWireBlock.WIRE_CONNECTION_EAST, WireConnection.SIDE), 4, -3, 1); + finder.addBlock(Blocks.MOSSY_COBBLESTONE.getDefaultState(), 3, -3, 1); + + finder.addBlock(Blocks.VINE.getDefaultState().with(VineBlock.SOUTH, true), 3, -2, 2); + finder.addBlock((Blocks.TRIPWIRE_HOOK.getDefaultState().with(TripwireHookBlock.FACING, Direction.NORTH)).with(TripwireHookBlock.ATTACHED, true), 7, -3, 1); + finder.addBlock((Blocks.TRIPWIRE_HOOK.getDefaultState().with(TripwireHookBlock.FACING, Direction.SOUTH)).with(TripwireHookBlock.ATTACHED, true), 7, -3, 5); + finder.addBlock(((Blocks.TRIPWIRE.getDefaultState().with(TripwireBlock.NORTH, true)).with(TripwireBlock.SOUTH, true)).with(TripwireBlock.ATTACHED, true), 7, -3, 2); + finder.addBlock(((Blocks.TRIPWIRE.getDefaultState().with(TripwireBlock.NORTH, true)).with(TripwireBlock.SOUTH, true)).with(TripwireBlock.ATTACHED, true), 7, -3, 3); + finder.addBlock(((Blocks.TRIPWIRE.getDefaultState().with(TripwireBlock.NORTH, true)).with(TripwireBlock.SOUTH, true)).with(TripwireBlock.ATTACHED, true), 7, -3, 4); + finder.addBlock(Blocks.REDSTONE_WIRE.getDefaultState().with(RedstoneWireBlock.WIRE_CONNECTION_EAST, WireConnection.SIDE), 8, -3, 6); + finder.addBlock((Blocks.REDSTONE_WIRE.getDefaultState().with(RedstoneWireBlock.WIRE_CONNECTION_WEST, WireConnection.SIDE)).with(RedstoneWireBlock.WIRE_CONNECTION_SOUTH, WireConnection.SIDE), 9, -3, 6); + finder.addBlock((Blocks.REDSTONE_WIRE.getDefaultState().with(RedstoneWireBlock.WIRE_CONNECTION_NORTH, WireConnection.SIDE)).with(RedstoneWireBlock.WIRE_CONNECTION_SOUTH, WireConnection.UP), 9, -3, 5); + finder.addBlock(Blocks.MOSSY_COBBLESTONE.getDefaultState(), 9, -3, 4); + finder.addBlock(Blocks.REDSTONE_WIRE.getDefaultState().with(RedstoneWireBlock.WIRE_CONNECTION_NORTH, WireConnection.SIDE), 9, -2, 4); + + finder.addBlock(Blocks.VINE.getDefaultState().with(VineBlock.EAST, true), 8, -1, 3); + finder.addBlock(Blocks.VINE.getDefaultState().with(VineBlock.EAST, true), 8, -2, 3); + + finder.addBlock(Blocks.MOSSY_COBBLESTONE.getDefaultState(), 9, -3, 2); + finder.addBlock(Blocks.MOSSY_COBBLESTONE.getDefaultState(), 8, -3, 1); + finder.addBlock(Blocks.MOSSY_COBBLESTONE.getDefaultState(), 4, -3, 5); + finder.addBlock(Blocks.MOSSY_COBBLESTONE.getDefaultState(), 5, -2, 5); + finder.addBlock(Blocks.MOSSY_COBBLESTONE.getDefaultState(), 5, -1, 5); + finder.addBlock(Blocks.MOSSY_COBBLESTONE.getDefaultState(), 6, -3, 5); + finder.addBlock(Blocks.MOSSY_COBBLESTONE.getDefaultState(), 7, -2, 5); + finder.addBlock(Blocks.MOSSY_COBBLESTONE.getDefaultState(), 7, -1, 5); + finder.addBlock(Blocks.MOSSY_COBBLESTONE.getDefaultState(), 8, -3, 5); + finder.addBlock(Blocks.CHISELED_STONE_BRICKS.getDefaultState(), 8, -2, 11); + finder.addBlock(Blocks.CHISELED_STONE_BRICKS.getDefaultState(), 9, -2, 11); + finder.addBlock(Blocks.CHISELED_STONE_BRICKS.getDefaultState(), 10, -2, 11); + BlockState blockState_6 = (Blocks.LEVER.getDefaultState().with(LeverBlock.FACING, Direction.NORTH)).with(LeverBlock.FACE, WallMountLocation.WALL); + finder.addBlock(blockState_6, 8, -2, 12); + finder.addBlock(blockState_6, 9, -2, 12); + finder.addBlock(blockState_6, 10, -2, 12); + finder.addBlock(Blocks.MOSSY_COBBLESTONE.getDefaultState(), 10, -2, 9); + finder.addBlock(Blocks.REDSTONE_WIRE.getDefaultState().with(RedstoneWireBlock.WIRE_CONNECTION_NORTH, WireConnection.SIDE), 8, -2, 9); + finder.addBlock(Blocks.REDSTONE_WIRE.getDefaultState().with(RedstoneWireBlock.WIRE_CONNECTION_SOUTH, WireConnection.SIDE), 8, -2, 10); + finder.addBlock(Blocks.REDSTONE_WIRE.getDefaultState(), 10, -1, 9); + finder.addBlock(Blocks.STICKY_PISTON.getDefaultState().with(PistonBlock.FACING, Direction.UP), 9, -2, 8); + finder.addBlock(Blocks.STICKY_PISTON.getDefaultState().with(PistonBlock.FACING, Direction.WEST), 10, -2, 8); + finder.addBlock(Blocks.STICKY_PISTON.getDefaultState().with(PistonBlock.FACING, Direction.WEST), 10, -1, 8); + finder.addBlock(Blocks.REPEATER.getDefaultState().with(RepeaterBlock.FACING, Direction.NORTH), 10, -2, 10); + } + + public static List create(World world, ChunkPos chunkPos) { + List finders = new ArrayList<>(); + finders.add(new JunglePyramidFinder(world, chunkPos)); + return finders; + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/finder/structure/MansionFinder.java b/src/main/java/kaptainwutax/seedcracker/finder/structure/MansionFinder.java new file mode 100644 index 00000000..5b58825c --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/finder/structure/MansionFinder.java @@ -0,0 +1,123 @@ +package kaptainwutax.seedcracker.finder.structure; + +import kaptainwutax.featureutils.structure.RegionStructure; +import kaptainwutax.seedcracker.Features; +import kaptainwutax.seedcracker.SeedCracker; +import kaptainwutax.seedcracker.cracker.DataAddedEvent; +import kaptainwutax.seedcracker.finder.Finder; +import kaptainwutax.seedcracker.render.Color; +import kaptainwutax.seedcracker.render.Cube; +import kaptainwutax.seedcracker.render.Cuboid; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3i; +import net.minecraft.world.World; +import net.minecraft.world.biome.Biome; +import net.minecraft.world.dimension.DimensionType; +import net.minecraft.world.gen.feature.StructureFeature; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class MansionFinder extends Finder { + + protected static List SEARCH_POSITIONS = buildSearchPositions(CHUNK_POSITIONS, pos -> { + if((pos.getX() & 15) != 0)return true; + if((pos.getZ() & 15) != 0)return true; + return false; + }); + + protected List finders = new ArrayList<>(); + protected Vec3i size = new Vec3i(16, 8, 16); + + public MansionFinder(World world, ChunkPos chunkPos) { + super(world, chunkPos); + + for(Direction direction: Direction.values()) { + PieceFinder finder = new PieceFinder(world, chunkPos, direction, this.size); + + finder.searchPositions = SEARCH_POSITIONS; + + buildStructure(finder); + this.finders.add(finder); + } + } + + @Override + public List findInChunk() { + Biome biome = this.world.getBiomeForNoiseGen((this.chunkPos.x << 2) + 2, 0, (this.chunkPos.z << 2) + 2); + + if(!biome.getGenerationSettings().hasStructureFeature(StructureFeature.MANSION)) { + return new ArrayList<>(); + } + + Map> result = this.findInChunkPieces(); + List combinedResult = new ArrayList<>(); + + result.forEach((pieceFinder, positions) -> { + combinedResult.addAll(positions); + + positions.forEach(pos -> { + RegionStructure.Data data = Features.MANSION.at(this.chunkPos.x, this.chunkPos.z); + + if(SeedCracker.get().getDataStorage().addBaseData(data, DataAddedEvent.POKE_STRUCTURES)) { + this.renderers.add(new Cuboid(pos, pieceFinder.getLayout(), new Color(102, 66, 33))); + this.renderers.add(new Cube(this.chunkPos.getCenterBlockPos().add(0, pos.getY(), 0), new Color(102, 66, 33))); + } + }); + }); + + return combinedResult; + } + + public Map> findInChunkPieces() { + Map> result = new HashMap<>(); + + this.finders.forEach(pieceFinder -> { + result.put(pieceFinder, pieceFinder.findInChunk()); + }); + + return result; + } + + public void buildStructure(PieceFinder finder) { + BlockState air = Blocks.AIR.getDefaultState(); + BlockState cobblestone = Blocks.COBBLESTONE.getDefaultState(); + BlockState birchPlanks = Blocks.BIRCH_PLANKS.getDefaultState(); + BlockState redCarpet = Blocks.RED_CARPET.getDefaultState(); + BlockState whiteCarpet = Blocks.WHITE_CARPET.getDefaultState(); + + finder.fillWithOutline(0, 0, 0, 15, 0, 15, birchPlanks, birchPlanks, false); + finder.fillWithOutline(0, 0, 8, 6, 0, 12, null, null, false); + finder.fillWithOutline(0, 0, 12, 9, 0, 15, null, null, false); + finder.fillWithOutline(15, 0, 0, 15, 0, 15, cobblestone, cobblestone, false); + finder.addBlock(Blocks.DARK_OAK_LOG.getDefaultState(), 15, 0, 15); + finder.addBlock(Blocks.DARK_OAK_LOG.getDefaultState(), 15, 0, 7); + finder.addBlock(Blocks.DARK_OAK_LOG.getDefaultState(), 14, 0, 7); + + finder.fillWithOutline(9, 1, 0, 9, 1, 8, whiteCarpet, whiteCarpet, false); + finder.addBlock(whiteCarpet, 8,1, 8); + finder.fillWithOutline(13, 1, 0, 13, 1, 8, whiteCarpet, whiteCarpet, false); + finder.addBlock(whiteCarpet, 14,1, 8); + + finder.fillWithOutline(10, 1, 0, 12, 1, 15, redCarpet, redCarpet, false); + } + + @Override + public boolean isValidDimension(DimensionType dimension) { + return this.isOverworld(dimension); + } + + public static List create(World world, ChunkPos chunkPos) { + List finders = new ArrayList<>(); + finders.add(new MansionFinder(world, chunkPos)); + return finders; + } + +} + diff --git a/src/main/java/kaptainwutax/seedcracker/finder/structure/MonumentFinder.java b/src/main/java/kaptainwutax/seedcracker/finder/structure/MonumentFinder.java new file mode 100644 index 00000000..e53b1452 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/finder/structure/MonumentFinder.java @@ -0,0 +1,139 @@ +package kaptainwutax.seedcracker.finder.structure; + +import kaptainwutax.featureutils.structure.RegionStructure; +import kaptainwutax.seedcracker.Features; +import kaptainwutax.seedcracker.SeedCracker; +import kaptainwutax.seedcracker.cracker.DataAddedEvent; +import kaptainwutax.seedcracker.finder.Finder; +import kaptainwutax.seedcracker.render.Color; +import kaptainwutax.seedcracker.render.Cube; +import kaptainwutax.seedcracker.render.Cuboid; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3i; +import net.minecraft.world.World; +import net.minecraft.world.dimension.DimensionType; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class MonumentFinder extends Finder { + + protected static List SEARCH_POSITIONS = buildSearchPositions(CHUNK_POSITIONS, pos -> { + if(pos.getY() != 56)return true; + return false; + }); + + protected List finders = new ArrayList<>(); + protected final Vec3i size = new Vec3i(8, 5, 8); + + public MonumentFinder(World world, ChunkPos chunkPos) { + super(world, chunkPos); + + PieceFinder finder = new PieceFinder(world, chunkPos, Direction.NORTH, size); + + finder.searchPositions = SEARCH_POSITIONS; + + buildStructure(finder); + this.finders.add(finder); + } + + @Override + public List findInChunk() { + Map> result = this.findInChunkPieces(); + List combinedResult = new ArrayList<>(); + + result.forEach((pieceFinder, positions) -> { + positions.removeIf(pos -> { + //Figure this out, it's not a trivial task. + return false; + }); + + combinedResult.addAll(positions); + + positions.forEach(pos -> { + ChunkPos monumentStart = new ChunkPos(this.chunkPos.x + 1, this.chunkPos.z + 1); + RegionStructure.Data data = Features.MONUMENT.at(monumentStart.x, monumentStart.z); + + if(SeedCracker.get().getDataStorage().addBaseData(data, DataAddedEvent.POKE_STRUCTURES)) { + this.renderers.add(new Cuboid(pos, pieceFinder.getLayout(), new Color(0, 0, 255))); + this.renderers.add(new Cube(monumentStart.getCenterBlockPos().add(0, pos.getY(), 0), new Color(0, 0, 255))); + } + }); + }); + + return combinedResult; + } + + public Map> findInChunkPieces() { + Map> result = new HashMap<>(); + + this.finders.forEach(pieceFinder -> { + result.put(pieceFinder, pieceFinder.findInChunk()); + }); + + return result; + } + + @Override + public boolean isValidDimension(DimensionType dimension) { + return this.isOverworld(dimension); + } + + public void buildStructure(PieceFinder finder) { + BlockState prismarine = Blocks.PRISMARINE.getDefaultState(); + BlockState prismarineBricks = Blocks.PRISMARINE_BRICKS.getDefaultState(); + BlockState darkPrismarine = Blocks.DARK_PRISMARINE.getDefaultState(); + BlockState seaLantern = Blocks.SEA_LANTERN.getDefaultState(); + BlockState water = Blocks.WATER.getDefaultState(); + + //4 bottom pillars. + for(int i = 0; i < 4; i++) { + int x = i >= 2 ? 7 : 0; + int z = i % 2 == 0 ? 0 : 7; + + for(int y = 0; y < 3; y++) { + finder.addBlock(prismarineBricks, x, y, z); + } + } + + //First bend. + for(int i = 0; i < 4; i++) { + int x = i >= 2 ? 6 : 1; + int z = i % 2 == 0 ? 1 : 6; + finder.addBlock(prismarineBricks, x, 3, z); + } + + //Prismarine ring. + for(int x = 2; x <= 5; x++) { + for(int z = 2; z <= 5; z++) { + if(x == 2 || x == 5 || z == 2 || z == 5) { + finder.addBlock(prismarine, x, 4, z); + } + } + } + + //Second bend. + for(int i = 0; i < 4; i++) { + int x = i >= 2 ? 5 : 2; + int z = i % 2 == 0 ? 2 : 5; + finder.addBlock(prismarineBricks, x, 4, z); + finder.addBlock(seaLantern, x, 3, z); + } + } + + public static List create(World world, ChunkPos chunkPos) { + List finders = new ArrayList<>(); + finders.add(new MonumentFinder(world, chunkPos)); + finders.add(new MonumentFinder(world, new ChunkPos(chunkPos.x - 1, chunkPos.z))); + finders.add(new MonumentFinder(world, new ChunkPos(chunkPos.x, chunkPos.z - 1))); + finders.add(new MonumentFinder(world, new ChunkPos(chunkPos.x - 1, chunkPos.z - 1))); + return finders; + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/finder/structure/PieceFinder.java b/src/main/java/kaptainwutax/seedcracker/finder/structure/PieceFinder.java new file mode 100644 index 00000000..b6df2584 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/finder/structure/PieceFinder.java @@ -0,0 +1,237 @@ +package kaptainwutax.seedcracker.finder.structure; + +import kaptainwutax.seedcracker.finder.Finder; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.client.MinecraftClient; +import net.minecraft.util.BlockMirror; +import net.minecraft.util.BlockRotation; +import net.minecraft.util.math.*; +import net.minecraft.world.World; +import net.minecraft.world.dimension.DimensionType; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class PieceFinder extends Finder { + + protected Map structure = new LinkedHashMap<>(); + private BlockBox boundingBox; + protected List searchPositions = new ArrayList<>(); + + protected Direction facing; + private BlockMirror mirror; + private BlockRotation rotation; + + protected int width; + protected int height; + protected int depth; + + private boolean debug; + + public PieceFinder(World world, ChunkPos chunkPos, Direction facing, Vec3i size) { + super(world, chunkPos); + + this.setOrientation(facing); + this.width = size.getX(); + this.height = size.getY(); + this.depth = size.getZ(); + + if(this.facing.getAxis() == Direction.Axis.Z) { + this.boundingBox = new BlockBox( + 0, 0, 0, + size.getX() - 1, size.getY() - 1, size.getZ() - 1 + ); + } else { + this.boundingBox = new BlockBox( + 0, 0, 0, + size.getZ() - 1, size.getY() - 1, size.getX() - 1 + ); + } + } + + public Vec3i getLayout() { + if(this.facing.getAxis() != Direction.Axis.Z) { + return new Vec3i(this.depth, this.height, this.width); + } + + return new Vec3i(this.width, this.height, this.depth); + } + + @Override + public List findInChunk() { + List result = new ArrayList<>(); + + if(this.structure.isEmpty()) { + return result; + } + + //FOR DEBUGGING PIECES. + if(this.debug) { + MinecraftClient.getInstance().execute(() -> { + int y = this.rotation.ordinal() * 10 + this.mirror.ordinal() * 20 + 120; + + if (this.chunkPos.x % 2 == 0 && this.chunkPos.z % 2 == 0) { + this.structure.forEach((pos, state) -> { + this.world.setBlockState(this.chunkPos.getCenterBlockPos().add(pos).add(0, y, 0), state, 0); + }); + } + }); + } + + for(BlockPos center: this.searchPositions) { + boolean found = true; + + for(Map.Entry entry: this.structure.entrySet()) { + BlockPos pos = this.chunkPos.getCenterBlockPos().add(center.add(entry.getKey())); + BlockState state = this.world.getBlockState(pos); + + //Blockstate may change when it gets placed in the world, that's why it's using the block here. + if(entry.getValue() != null && !state.getBlock().equals(entry.getValue().getBlock())) { + found = false; + break; + } + } + + if(found) { + result.add(this.chunkPos.getCenterBlockPos().add(center)); + } + } + + return result; + } + + public void setOrientation(Direction facing) { + this.facing = facing; + + if(facing == null) { + this.rotation = BlockRotation.NONE; + this.mirror = BlockMirror.NONE; + } else { + switch(facing) { + case SOUTH: + this.mirror = BlockMirror.LEFT_RIGHT; + this.rotation = BlockRotation.NONE; + break; + case WEST: + this.mirror = BlockMirror.LEFT_RIGHT; + this.rotation = BlockRotation.CLOCKWISE_90; + break; + case EAST: + this.mirror = BlockMirror.NONE; + this.rotation = BlockRotation.CLOCKWISE_90; + break; + default: + this.mirror = BlockMirror.NONE; + this.rotation = BlockRotation.NONE; + } + } + + } + + protected int applyXTransform(int x, int z) { + if (this.facing == null) { + return x; + } else { + switch(this.facing) { + case NORTH: + case SOUTH: + return this.boundingBox.minX + x; + case WEST: + return this.boundingBox.maxX - z; + case EAST: + return this.boundingBox.minX + z; + default: + return x; + } + } + } + + protected int applyYTransform(int y) { + return this.facing == null ? y : y + this.boundingBox.minY; + } + + protected int applyZTransform(int x, int z) { + if (this.facing == null) { + return z; + } else { + switch(this.facing) { + case NORTH: + return this.boundingBox.maxZ - z; + case SOUTH: + return this.boundingBox.minZ + z; + case WEST: + case EAST: + return this.boundingBox.minZ + x; + default: + return z; + } + } + } + + protected BlockState getBlockAt(int ox, int oy, int oz) { + int x = this.applyXTransform(ox, oz); + int y = this.applyYTransform(oy); + int z = this.applyZTransform(ox, oz); + BlockPos pos = new BlockPos(x, y, z); + + return !this.boundingBox.contains(pos) ? + Blocks.AIR.getDefaultState() : + this.structure.getOrDefault(pos, Blocks.AIR.getDefaultState()); + } + + protected void fillWithOutline(int minX, int minY, int minZ, int maxX, int maxY, int maxZ, BlockState outline, BlockState inside, boolean onlyReplaceAir) { + for(int y = minY; y <= maxY; ++y) { + for(int x = minX; x <= maxX; ++x) { + for(int z = minZ; z <= maxZ; ++z) { + if(!onlyReplaceAir || !this.getBlockAt(x, y, z).isAir()) { + if(y != minY && y != maxY && x != minX && x != maxX && z != minZ && z != maxZ) { + this.addBlock(inside, x, y, z); + } else { + this.addBlock(outline, x, y, z); + } + } + } + } + } + + } + + protected void addBlock(BlockState state, int x, int y, int z) { + BlockPos pos = new BlockPos( + this.applyXTransform(x, z), + this.applyYTransform(y), + this.applyZTransform(x, z) + ); + + if(this.boundingBox.contains(pos)) { + if(state == null) { + this.structure.remove(pos); + return; + } + + if (this.mirror != BlockMirror.NONE) { + state = state.mirror(this.mirror); + } + + if (this.rotation != BlockRotation.NONE) { + state = state.rotate(this.rotation); + } + + + this.structure.put(pos, state); + } + } + + @Override + public boolean isValidDimension(DimensionType dimension) { + return true; + } + + public void setDebug() { + this.debug = true; + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/finder/structure/ShipwreckFinder.java b/src/main/java/kaptainwutax/seedcracker/finder/structure/ShipwreckFinder.java new file mode 100644 index 00000000..f4967b74 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/finder/structure/ShipwreckFinder.java @@ -0,0 +1,217 @@ +package kaptainwutax.seedcracker.finder.structure; + +import kaptainwutax.featureutils.structure.RegionStructure; +import kaptainwutax.seedcracker.Features; +import kaptainwutax.seedcracker.SeedCracker; +import kaptainwutax.seedcracker.cracker.DataAddedEvent; +import kaptainwutax.seedcracker.finder.BlockFinder; +import kaptainwutax.seedcracker.finder.Finder; +import kaptainwutax.seedcracker.render.Color; +import kaptainwutax.seedcracker.render.Cube; +import kaptainwutax.seedcracker.render.Cuboid; +import net.minecraft.block.*; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.ChestBlockEntity; +import net.minecraft.block.enums.ChestType; +import net.minecraft.tag.BlockTags; +import net.minecraft.util.math.BlockBox; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.World; +import net.minecraft.world.biome.Biome; +import net.minecraft.world.dimension.DimensionType; +import net.minecraft.world.gen.feature.StructureFeature; + +import java.util.ArrayList; +import java.util.List; + +public class ShipwreckFinder extends BlockFinder { + + protected static List SEARCH_POSITIONS = Finder.buildSearchPositions(Finder.CHUNK_POSITIONS, pos -> { + return false; + }); + + public ShipwreckFinder(World world, ChunkPos chunkPos) { + super(world, chunkPos, Blocks.CHEST); + this.searchPositions = SEARCH_POSITIONS; + } + + @Override + public List findInChunk() { + Biome biome = this.world.getBiomeForNoiseGen((this.chunkPos.x << 2) + 2, 0, (this.chunkPos.z << 2) + 2); + + if(!biome.getGenerationSettings().hasStructureFeature(StructureFeature.SHIPWRECK)) { + return new ArrayList<>(); + } + + List result = super.findInChunk(); + + result.removeIf(pos -> { + BlockState state = this.world.getBlockState(pos); + if(state.get(ChestBlock.CHEST_TYPE) != ChestType.SINGLE)return true; + + BlockEntity blockEntity = this.world.getBlockEntity(pos); + if(!(blockEntity instanceof ChestBlockEntity))return true; + + return !this.onChestFound(pos); + }); + + return result; + } + + /** + * Source: https://github.com/skyrising/casual-mod/blob/master/src/main/java/de/skyrising/casual/ShipwreckFinder.java + * */ + private boolean onChestFound(BlockPos pos) { + BlockPos.Mutable mutablePos = new BlockPos.Mutable(pos.getX(), pos.getY(), pos.getZ()); + Direction chestFacing = world.getBlockState(pos).get(ChestBlock.FACING); + + int[] stairs = new int[4]; + int totalStairs = 0; + int[] trapdoors = new int[4]; + int totalTrapdoors = 0; + for(int y = -1; y <= 2; y++) { + for(int x = -1; x <= 1; x++) { + for(int z = -1; z <= 1; z++) { + if (x == 0 && y == 0 && z == 0)continue; + mutablePos.set(pos.getX() + x, pos.getY() + y, pos.getZ() + z); + BlockState neighborState = world.getBlockState(mutablePos); + Block neighborBlock = neighborState.getBlock(); + if(neighborBlock == Blocks.VOID_AIR)return false; + + if(neighborBlock instanceof StairsBlock) { + stairs[y + 1]++; + totalStairs++; + } else if(neighborBlock instanceof TrapdoorBlock) { + trapdoors[y + 1]++; + totalTrapdoors++; + } + } + } + } + //System.out.printf("%s: chest facing %s\n", pos, chestFacing); + int chestX = 4; + int chestY = 2; + int chestZ = 0; + int length = 16; + int height = 9; + Direction direction = chestFacing; + + if(trapdoors[3] > 4) { // with_mast[_degraded] + chestZ = 9; + height = 21; + length = 28; + } else if(totalTrapdoors == 0 && stairs[3] == 3) { // upsidedown_backhalf[_degraded] + if(stairs[0] == 0) { + chestX = 2; + chestZ = 12; + direction = chestFacing.getOpposite(); + } else { // redundant + chestX = 3; + chestY = 5; + chestZ = 5; + direction = chestFacing.rotateYClockwise(); + } + } else if(totalTrapdoors == 0) { // rightsideup that have backhalf + if(stairs[0] == 4) { + if(totalStairs > 4) { + chestX = 6; + chestY = 4; + chestZ = 12; + direction = chestFacing.getOpposite(); + } else { // sideways backhalf + chestX = 6; + chestY = 3; + chestZ = 8; + length = 17; + direction = chestFacing.getOpposite(); + } + } else if(stairs[0] == 3 && totalStairs > 5) { + chestX = 5; + chestZ = 6; + direction = chestFacing.rotateYCounterclockwise(); + } + + mutablePos.set(pos); + mutablePos.move(0, -chestY, 0); + mutablePos.move(direction.rotateYClockwise(), chestX - 4); + mutablePos.move(direction, -chestZ - 1); + + if(this.world.getBlockState(mutablePos).getMaterial() == Material.WOOD) { + if(length == 17) { // sideways + chestZ += 11; + length += 11; + } else { + chestZ += 12; + length += 12; + } + mutablePos.move(0, 10, 0); + + BlockState b = this.world.getBlockState(mutablePos); + + if(this.world.getBlockState(mutablePos).isIn(BlockTags.LOGS)) { + height = 21; + } + } + } else if(totalTrapdoors == 2 && trapdoors[3] == 2 && stairs[3] == 3) { // rightsideup_fronthalf[_degraded] + chestZ = 8; + length = 24; + } + + if(chestZ != 0) { + mutablePos.set(pos); + mutablePos.move(direction, 15 - chestZ); + mutablePos.move(direction.rotateYClockwise(), chestX - 4); + BlockPos.Mutable pos2 = new BlockPos.Mutable(mutablePos.getX(), mutablePos.getY(), mutablePos.getZ()); + pos2.move(0, -chestY, 0); + pos2.move(direction, -15); + pos2.move(direction.rotateYClockwise(), 4); + BlockPos.Mutable pos3 = new BlockPos.Mutable(pos2.getX(), pos2.getY(), pos2.getZ()); + pos3.move(direction, length - 1); + pos3.move(direction.rotateYClockwise(), -8); + pos3.move(0, height - 1, 0); + + BlockBox box = new BlockBox( + Math.min(pos2.getX(), pos3.getX()), pos2.getY(), Math.min(pos2.getZ(), pos3.getZ()), + Math.max(pos2.getX(), pos3.getX()) + 1, pos3.getY() + 1, Math.max(pos2.getZ(), pos3.getZ()) + 1); + + mutablePos.move(-4, -chestY, -15); + + if((mutablePos.getX() & 0xf) == 0 && (mutablePos.getZ() & 0xf) == 0) { + RegionStructure.Data data = Features.SHIPWRECK.at(new ChunkPos(mutablePos).x, new ChunkPos(mutablePos).z); + + if(SeedCracker.get().getDataStorage().addBaseData(data, DataAddedEvent.POKE_STRUCTURES)) { + this.renderers.add(new Cuboid(box, new Color(0, 255, 255))); + this.renderers.add(new Cube(new ChunkPos(mutablePos).getCenterBlockPos().offset(Direction.UP, mutablePos.getY()), new Color(0, 255, 255))); + return true; + } + } + } + + return false; + } + + @Override + public boolean isValidDimension(DimensionType dimension) { + return this.isOverworld(dimension); + } + + public static List create(World world, ChunkPos chunkPos) { + List finders = new ArrayList<>(); + finders.add(new ShipwreckFinder(world, chunkPos)); + + finders.add(new ShipwreckFinder(world, new ChunkPos(chunkPos.x - 1, chunkPos.z))); + finders.add(new ShipwreckFinder(world, new ChunkPos(chunkPos.x, chunkPos.z - 1))); + finders.add(new ShipwreckFinder(world, new ChunkPos(chunkPos.x - 1, chunkPos.z - 1))); + + finders.add(new ShipwreckFinder(world, new ChunkPos(chunkPos.x + 1, chunkPos.z))); + finders.add(new ShipwreckFinder(world, new ChunkPos(chunkPos.x, chunkPos.z + 1))); + finders.add(new ShipwreckFinder(world, new ChunkPos(chunkPos.x + 1, chunkPos.z + 1))); + + finders.add(new ShipwreckFinder(world, new ChunkPos(chunkPos.x - 1, chunkPos.z - 1))); + finders.add(new ShipwreckFinder(world, new ChunkPos(chunkPos.x - 1, chunkPos.z + 1))); + return finders; + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/finder/structure/SwampHutFinder.java b/src/main/java/kaptainwutax/seedcracker/finder/structure/SwampHutFinder.java new file mode 100644 index 00000000..07984401 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/finder/structure/SwampHutFinder.java @@ -0,0 +1,98 @@ +package kaptainwutax.seedcracker.finder.structure; + +import kaptainwutax.featureutils.structure.RegionStructure; +import kaptainwutax.seedcracker.Features; +import kaptainwutax.seedcracker.SeedCracker; +import kaptainwutax.seedcracker.cracker.DataAddedEvent; +import kaptainwutax.seedcracker.finder.Finder; +import kaptainwutax.seedcracker.render.Color; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.block.StairsBlock; +import net.minecraft.block.enums.StairShape; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3i; +import net.minecraft.world.World; +import net.minecraft.world.gen.feature.StructureFeature; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class SwampHutFinder extends AbstractTempleFinder { + + public SwampHutFinder(World world, ChunkPos chunkPos) { + super(world, chunkPos, new Vec3i(7, 7, 9)); + } + + @Override + public List findInChunk() { + Map> result = super.findInChunkPieces(); + List combinedResult = new ArrayList<>(); + + result.forEach((pieceFinder, positions) -> { + combinedResult.addAll(positions); + + positions.forEach(pos -> { + RegionStructure.Data data = Features.SWAMP_HUT.at(this.chunkPos.x, this.chunkPos.z); + + if(SeedCracker.get().getDataStorage().addBaseData(data, DataAddedEvent.POKE_STRUCTURES)) { + this.addRenderers(pieceFinder, pos, new Color(255, 0, 255)); + } + }); + }); + + return combinedResult; + } + + @Override + protected StructureFeature getStructureFeature() { + return StructureFeature.SWAMP_HUT; + } + + @Override + public void buildStructure(PieceFinder finder) { + finder.fillWithOutline(1, 1, 1, 5, 1, 7, Blocks.SPRUCE_PLANKS.getDefaultState(), Blocks.SPRUCE_PLANKS.getDefaultState(), false); + finder.fillWithOutline(1, 4, 2, 5, 4, 7, Blocks.SPRUCE_PLANKS.getDefaultState(), Blocks.SPRUCE_PLANKS.getDefaultState(), false); + finder.fillWithOutline(2, 1, 0, 4, 1, 0, Blocks.SPRUCE_PLANKS.getDefaultState(), Blocks.SPRUCE_PLANKS.getDefaultState(), false); + finder.fillWithOutline(2, 2, 2, 3, 3, 2, Blocks.SPRUCE_PLANKS.getDefaultState(), Blocks.SPRUCE_PLANKS.getDefaultState(), false); + finder.fillWithOutline(1, 2, 3, 1, 3, 6, Blocks.SPRUCE_PLANKS.getDefaultState(), Blocks.SPRUCE_PLANKS.getDefaultState(), false); + finder.fillWithOutline(5, 2, 3, 5, 3, 6, Blocks.SPRUCE_PLANKS.getDefaultState(), Blocks.SPRUCE_PLANKS.getDefaultState(), false); + finder.fillWithOutline(2, 2, 7, 4, 3, 7, Blocks.SPRUCE_PLANKS.getDefaultState(), Blocks.SPRUCE_PLANKS.getDefaultState(), false); + finder.fillWithOutline(1, 0, 2, 1, 3, 2, Blocks.OAK_LOG.getDefaultState(), Blocks.OAK_LOG.getDefaultState(), false); + finder.fillWithOutline(5, 0, 2, 5, 3, 2, Blocks.OAK_LOG.getDefaultState(), Blocks.OAK_LOG.getDefaultState(), false); + finder.fillWithOutline(1, 0, 7, 1, 3, 7, Blocks.OAK_LOG.getDefaultState(), Blocks.OAK_LOG.getDefaultState(), false); + finder.fillWithOutline(5, 0, 7, 5, 3, 7, Blocks.OAK_LOG.getDefaultState(), Blocks.OAK_LOG.getDefaultState(), false); + finder.addBlock(Blocks.OAK_FENCE.getDefaultState(), 2, 3, 2); + finder.addBlock(Blocks.OAK_FENCE.getDefaultState(), 3, 3, 7); + finder.addBlock(Blocks.AIR.getDefaultState(), 1, 3, 4); + finder.addBlock(Blocks.AIR.getDefaultState(), 5, 3, 4); + finder.addBlock(Blocks.AIR.getDefaultState(), 5, 3, 5); + finder.addBlock(Blocks.POTTED_RED_MUSHROOM.getDefaultState(), 1, 3, 5); + finder.addBlock(Blocks.CRAFTING_TABLE.getDefaultState(), 3, 2, 6); + finder.addBlock(Blocks.CAULDRON.getDefaultState(), 4, 2, 6); + finder.addBlock(Blocks.OAK_FENCE.getDefaultState(), 1, 2, 1); + finder.addBlock(Blocks.OAK_FENCE.getDefaultState(), 5, 2, 1); + BlockState northStairs = Blocks.SPRUCE_STAIRS.getDefaultState().with(StairsBlock.FACING, Direction.NORTH); + BlockState eastStairs = Blocks.SPRUCE_STAIRS.getDefaultState().with(StairsBlock.FACING, Direction.EAST); + BlockState westStairs = Blocks.SPRUCE_STAIRS.getDefaultState().with(StairsBlock.FACING, Direction.WEST); + BlockState southStairs = Blocks.SPRUCE_STAIRS.getDefaultState().with(StairsBlock.FACING, Direction.SOUTH); + finder.fillWithOutline(0, 4, 1, 6, 4, 1, northStairs, northStairs, false); + finder.fillWithOutline(0, 4, 2, 0, 4, 7, eastStairs, eastStairs, false); + finder.fillWithOutline(6, 4, 2, 6, 4, 7, westStairs, westStairs, false); + finder.fillWithOutline(0, 4, 8, 6, 4, 8, southStairs, southStairs, false); + finder.addBlock(northStairs.with(StairsBlock.SHAPE, StairShape.OUTER_RIGHT), 0, 4, 1); + finder.addBlock(northStairs.with(StairsBlock.SHAPE, StairShape.OUTER_LEFT), 6, 4, 1); + finder.addBlock(southStairs.with(StairsBlock.SHAPE, StairShape.OUTER_LEFT), 0, 4, 8); + finder.addBlock(southStairs.with(StairsBlock.SHAPE, StairShape.OUTER_RIGHT), 6, 4, 8); + } + + public static List create(World world, ChunkPos chunkPos) { + List finders = new ArrayList<>(); + finders.add(new SwampHutFinder(world, chunkPos)); + return finders; + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/init/ClientCommands.java b/src/main/java/kaptainwutax/seedcracker/init/ClientCommands.java new file mode 100644 index 00000000..2f2a4dd3 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/init/ClientCommands.java @@ -0,0 +1,89 @@ +package kaptainwutax.seedcracker.init; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import kaptainwutax.seedcracker.command.*; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.command.CommandException; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.util.Formatting; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +public class ClientCommands { + + public static final String PREFIX = "seed"; + public static final List COMMANDS = new ArrayList<>(); + + public static RenderCommand RENDER; + public static FinderCommand FINDER; + public static DataCommand DATA; + public static CrackerCommand CRACKER; + public static VersionCommand VERSION; + + static { + COMMANDS.add(RENDER = new RenderCommand()); + COMMANDS.add(FINDER = new FinderCommand()); + COMMANDS.add(DATA = new DataCommand()); + COMMANDS.add(CRACKER = new CrackerCommand()); + COMMANDS.add(VERSION = new VersionCommand()); + } + + public static void registerCommands(CommandDispatcher dispatcher) { + COMMANDS.forEach(clientCommand -> clientCommand.register(dispatcher)); + } + + public static boolean isClientSideCommand(String[] args) { + if(args.length < 2)return false; + if(!PREFIX.equals(args[0]))return false; + + for(ClientCommand command: COMMANDS) { + if(command.getName().equals(args[1])) { + return true; + } + } + + return false; + } + + public static int executeCommand(StringReader reader) { + ClientPlayerEntity player = MinecraftClient.getInstance().player; + + try { + return player.networkHandler.getCommandDispatcher().execute(reader, new FakeCommandSource(player)); + } catch(CommandException e) { + ClientCommand.sendFeedback("ur bad, git gud command", Formatting.RED, false); + e.printStackTrace(); + } catch(CommandSyntaxException e) { + ClientCommand.sendFeedback("ur bad, git gud syntax", Formatting.RED, false); + e.printStackTrace(); + } catch(Exception e) { + ClientCommand.sendFeedback("ur bad, wat did u do", Formatting.RED, false); + e.printStackTrace(); + } + + return 1; + } + + /** + * Magic class by Earthcomputer. + * https://github.com/Earthcomputer/clientcommands/blob/fabric/src/main/java/net/earthcomputer/clientcommands/command/FakeCommandSource.java + * */ + public static class FakeCommandSource extends ServerCommandSource { + public FakeCommandSource(ClientPlayerEntity player) { + super(player, player.getPos(), player.getRotationClient(), null, 0, player.getEntityName(), player.getName(), null, player); + } + + @Override + public Collection getPlayerNames() { + return MinecraftClient.getInstance().getNetworkHandler().getPlayerList() + .stream().map(e -> e.getProfile().getName()).collect(Collectors.toList()); + } + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/mixin/ClientPlayNetworkHandlerMixin.java b/src/main/java/kaptainwutax/seedcracker/mixin/ClientPlayNetworkHandlerMixin.java new file mode 100644 index 00000000..8591f471 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/mixin/ClientPlayNetworkHandlerMixin.java @@ -0,0 +1,85 @@ +package kaptainwutax.seedcracker.mixin; + +import com.mojang.authlib.GameProfile; +import com.mojang.brigadier.CommandDispatcher; +import kaptainwutax.seedcracker.SeedCracker; +import kaptainwutax.seedcracker.cracker.DataAddedEvent; +import kaptainwutax.seedcracker.cracker.HashedSeedData; +import kaptainwutax.seedcracker.finder.FinderQueue; +import kaptainwutax.seedcracker.init.ClientCommands; +import kaptainwutax.seedcracker.util.Log; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.network.ClientConnection; +import net.minecraft.network.packet.s2c.play.ChunkDataS2CPacket; +import net.minecraft.network.packet.s2c.play.CommandTreeS2CPacket; +import net.minecraft.network.packet.s2c.play.GameJoinS2CPacket; +import net.minecraft.network.packet.s2c.play.PlayerRespawnS2CPacket; +import net.minecraft.server.command.CommandSource; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.util.math.ChunkPos; +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(ClientPlayNetworkHandler.class) +public abstract class ClientPlayNetworkHandlerMixin { + + @Shadow private ClientWorld world; + @Shadow private CommandDispatcher commandDispatcher; + + @Inject(method = "onChunkData", at = @At(value = "TAIL")) + private void onChunkData(ChunkDataS2CPacket packet, CallbackInfo ci) { + int chunkX = packet.getX(); + int chunkZ = packet.getZ(); + FinderQueue.get().onChunkData(this.world, new ChunkPos(chunkX, chunkZ)); + } + + @SuppressWarnings("unchecked") + @Inject(method = "", at = @At("RETURN")) + public void onInit(MinecraftClient mc, Screen screen, ClientConnection connection, GameProfile profile, CallbackInfo ci) { + ClientCommands.registerCommands((CommandDispatcher)(Object)this.commandDispatcher); + } + + @SuppressWarnings("unchecked") + @Inject(method = "onCommandTree", at = @At("TAIL")) + public void onOnCommandTree(CommandTreeS2CPacket packet, CallbackInfo ci) { + ClientCommands.registerCommands((CommandDispatcher)(Object)this.commandDispatcher); + } + + @Inject(method = "onGameJoin", at = @At(value = "TAIL")) + public void onGameJoin(GameJoinS2CPacket packet, CallbackInfo ci) { + //PRE-1.16 SUPPORTED GENERATOR TYPES + //GeneratorTypeData generatorTypeData = new GeneratorTypeData(packet.getGeneratorType()); + + //Log.warn("Fetched the generator type [" + + // I18n.translate(generatorTypeData.getGeneratorType().getStoredName()).toUpperCase() + "]."); + + //if(!SeedCracker.get().getDataStorage().addGeneratorTypeData(generatorTypeData)) { + // Log.error("THIS GENERATOR IS NOT SUPPORTED!"); + // Log.error("Overworld biome search WILL NOT run."); + //} + + HashedSeedData hashedSeedData = new HashedSeedData(packet.getSha256Seed()); + + if(SeedCracker.get().getDataStorage().addHashedSeedData(hashedSeedData, DataAddedEvent.POKE_BIOMES)) { + Log.warn("Fetched hashed world seed [" + hashedSeedData.getHashedSeed() + "]."); + } + + SeedCracker.get().setActive(SeedCracker.get().isActive()); + } + + @Inject(method = "onPlayerRespawn", at = @At(value = "TAIL")) + public void onPlayerRespawn(PlayerRespawnS2CPacket packet, CallbackInfo ci) { + HashedSeedData hashedSeedData = new HashedSeedData(packet.getSha256Seed()); + + if(SeedCracker.get().getDataStorage().addHashedSeedData(hashedSeedData, DataAddedEvent.POKE_BIOMES)) { + Log.warn("Fetched hashed world seed [" + hashedSeedData.getHashedSeed() + "]."); + } + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/mixin/ClientPlayerEntityMixin.java b/src/main/java/kaptainwutax/seedcracker/mixin/ClientPlayerEntityMixin.java new file mode 100644 index 00000000..1545b97e --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/mixin/ClientPlayerEntityMixin.java @@ -0,0 +1,38 @@ +package kaptainwutax.seedcracker.mixin; + +import com.mojang.brigadier.StringReader; +import kaptainwutax.seedcracker.SeedCracker; +import kaptainwutax.seedcracker.init.ClientCommands; +import net.minecraft.client.network.ClientPlayerEntity; +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.regex.Pattern; + +@Mixin(ClientPlayerEntity.class) +public abstract class ClientPlayerEntityMixin { + + @Inject(method = "tick", at = @At("HEAD")) + private void tick(CallbackInfo ci) { + SeedCracker.get().getDataStorage().tick(); + } + + @Inject(method = "sendChatMessage", at = @At("HEAD"), cancellable = true) + private void onSendChatMessage(String message, CallbackInfo ci) { + if(message.startsWith("/")) { + StringReader reader = new StringReader(message); + reader.skip(); + + int cursor = reader.getCursor(); + reader.setCursor(cursor); + + if(ClientCommands.isClientSideCommand(message.substring(1).split(Pattern.quote(" ")))) { + ClientCommands.executeCommand(reader); + ci.cancel(); + } + } + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/mixin/ClientWorldMixin.java b/src/main/java/kaptainwutax/seedcracker/mixin/ClientWorldMixin.java new file mode 100644 index 00000000..aab75540 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/mixin/ClientWorldMixin.java @@ -0,0 +1,26 @@ +package kaptainwutax.seedcracker.mixin; + +import kaptainwutax.seedcracker.SeedCracker; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.world.biome.Biome; +import net.minecraft.world.biome.Biomes; +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(ClientWorld.class) +public abstract class ClientWorldMixin { + + @Inject(method = "disconnect", at = @At("HEAD")) + private void disconnect(CallbackInfo ci) { + SeedCracker.get().reset(); + } + + @Inject(method = "getGeneratorStoredBiome", at = @At("HEAD"), cancellable = true) + private void getGeneratorStoredBiome(int x, int y, int z, CallbackInfoReturnable ci) { + ci.setReturnValue(Biomes.THE_VOID); + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/mixin/DimensionTypeMixin.java b/src/main/java/kaptainwutax/seedcracker/mixin/DimensionTypeMixin.java new file mode 100644 index 00000000..ff0561f5 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/mixin/DimensionTypeMixin.java @@ -0,0 +1,20 @@ +package kaptainwutax.seedcracker.mixin; + +import kaptainwutax.seedcracker.finder.Finder; +import net.minecraft.util.Identifier; +import net.minecraft.world.dimension.DimensionType; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(DimensionType.class) +public class DimensionTypeMixin implements Finder.DimensionTypeCaller { + + @Shadow @Final private Identifier infiniburn; + + @Override + public Identifier getInfiniburn() { + return this.infiniburn; + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/mixin/DummyProfilerMixin.java b/src/main/java/kaptainwutax/seedcracker/mixin/DummyProfilerMixin.java new file mode 100644 index 00000000..979bfc68 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/mixin/DummyProfilerMixin.java @@ -0,0 +1,18 @@ +package kaptainwutax.seedcracker.mixin; + +import kaptainwutax.seedcracker.render.RenderQueue; +import net.minecraft.util.profiler.DummyProfiler; +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(DummyProfiler.class) +public abstract class DummyProfilerMixin { + + @Inject(method = "swap(Ljava/lang/String;)V", at = @At("HEAD")) + private void swap(String type, CallbackInfo ci) { + RenderQueue.get().onRender(type); + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/mixin/GameRendererMixin.java b/src/main/java/kaptainwutax/seedcracker/mixin/GameRendererMixin.java new file mode 100644 index 00000000..b79d43bb --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/mixin/GameRendererMixin.java @@ -0,0 +1,24 @@ +package kaptainwutax.seedcracker.mixin; + +import kaptainwutax.seedcracker.render.RenderQueue; +import net.minecraft.client.render.GameRenderer; +import net.minecraft.client.util.math.MatrixStack; +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(GameRenderer.class) +public abstract class GameRendererMixin { + + @Inject(method = "renderWorld", at = @At("HEAD")) + private void renderWorldStart(float delta, long time, MatrixStack matrixStack, CallbackInfo ci) { + RenderQueue.get().setTrackRender(matrixStack); + } + + @Inject(method = "renderWorld", at = @At("TAIL")) + private void renderWorldFinish(float delta, long time, MatrixStack matrixStack, CallbackInfo ci) { + RenderQueue.get().setTrackRender(null); + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/mixin/ServerChunkManagerMixin.java b/src/main/java/kaptainwutax/seedcracker/mixin/ServerChunkManagerMixin.java new file mode 100644 index 00000000..871b49fe --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/mixin/ServerChunkManagerMixin.java @@ -0,0 +1,26 @@ +package kaptainwutax.seedcracker.mixin; + +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.server.world.ServerChunkManager; +import net.minecraft.server.world.ThreadedAnvilChunkStorage; +import org.spongepowered.asm.mixin.Final; +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.CallbackInfoReturnable; + +@Mixin(ServerChunkManager.class) +public class ServerChunkManagerMixin { + + @Shadow @Final public ThreadedAnvilChunkStorage threadedAnvilChunkStorage; + + @Inject(method = "getTotalChunksLoadedCount", at = @At("HEAD"), cancellable = true) + public void getTotalChunksLoadedCount(CallbackInfoReturnable ci) { + if(FabricLoader.getInstance().isDevelopmentEnvironment()) { + int count = this.threadedAnvilChunkStorage.getTotalChunksLoadedCount(); + if(count < 441)ci.setReturnValue(441); + } + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/profile/CustomProfile.java b/src/main/java/kaptainwutax/seedcracker/profile/CustomProfile.java new file mode 100644 index 00000000..5ccdf2d9 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/profile/CustomProfile.java @@ -0,0 +1,19 @@ +package kaptainwutax.seedcracker.profile; + +public abstract class CustomProfile extends FinderProfile { + + public CustomProfile(String author, boolean defaultState) { + super(defaultState); + this.author = author; + this.locked = false; + } + + public void setAuthor(String author) { + this.author = author; + } + + public void setLocked(boolean locked) { + this.locked = locked; + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/profile/FinderConfig.java b/src/main/java/kaptainwutax/seedcracker/profile/FinderConfig.java new file mode 100644 index 00000000..4e18b5b8 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/profile/FinderConfig.java @@ -0,0 +1,55 @@ +package kaptainwutax.seedcracker.profile; + +import kaptainwutax.seedcracker.finder.Finder; + +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.stream.Collectors; + +public class FinderConfig { + + protected FinderProfile finderProfile = new YoloProfile(); + protected Map> activeFinders = new ConcurrentHashMap<>(); + + public FinderConfig() { + + } + + public List getActiveFinderTypes() { + return this.finderProfile.typeStates.entrySet().stream() + .filter(Map.Entry::getValue) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + } + + public List getActiveFinders() { + this.activeFinders.values().forEach(finders -> { + finders.removeIf(Finder::isUseless); + }); + + return this.activeFinders.values().stream() + .flatMap(Queue::stream).collect(Collectors.toList()); + } + + public void addFinder(Finder.Type type, Finder finder) { + if(finder.isUseless())return; + + if(!this.activeFinders.containsKey(type)) { + this.activeFinders.put(type, new ConcurrentLinkedQueue<>()); + } + + this.activeFinders.get(type).add(finder); + } + + public boolean getActive(Finder.Type type) { + return this.finderProfile.typeStates.get(type); + } + + public boolean setActive(Finder.Type type, boolean flag) { + return this.finderProfile.setTypeState(type, flag); + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/profile/FinderProfile.java b/src/main/java/kaptainwutax/seedcracker/profile/FinderProfile.java new file mode 100644 index 00000000..ed06a9d5 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/profile/FinderProfile.java @@ -0,0 +1,34 @@ +package kaptainwutax.seedcracker.profile; + +import kaptainwutax.seedcracker.finder.Finder; + +import java.util.HashMap; + +public abstract class FinderProfile { + + public final HashMap typeStates = new HashMap<>(); + + protected String author; + protected boolean locked; + + public FinderProfile(boolean defaultState) { + for(Finder.Type type: Finder.Type.values()) { + this.typeStates.put(type, defaultState); + } + } + + public String getAuthor() { + return this.author; + } + + public boolean getLocked() { + return this.locked; + } + + public boolean setTypeState(Finder.Type type, boolean state) { + if(this.getLocked())return false; + this.typeStates.put(type, state); + return true; + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/profile/NopeProfile.java b/src/main/java/kaptainwutax/seedcracker/profile/NopeProfile.java new file mode 100644 index 00000000..7b9d8769 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/profile/NopeProfile.java @@ -0,0 +1,11 @@ +package kaptainwutax.seedcracker.profile; + +public class NopeProfile extends FinderProfile { + + public NopeProfile() { + super(false); + this.author = "KaptainWutax"; + this.locked = true; + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/profile/VanillaProfile.java b/src/main/java/kaptainwutax/seedcracker/profile/VanillaProfile.java new file mode 100644 index 00000000..2e006340 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/profile/VanillaProfile.java @@ -0,0 +1,13 @@ +package kaptainwutax.seedcracker.profile; + +import kaptainwutax.seedcracker.finder.Finder; + +public class VanillaProfile extends FinderProfile { + + public VanillaProfile() { + super(true); + this.author = "KaptainWutax"; + this.setTypeState(Finder.Type.DUNGEON, false); + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/profile/YoloProfile.java b/src/main/java/kaptainwutax/seedcracker/profile/YoloProfile.java new file mode 100644 index 00000000..8b128452 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/profile/YoloProfile.java @@ -0,0 +1,12 @@ +package kaptainwutax.seedcracker.profile; + +import kaptainwutax.seedcracker.finder.Finder; + +public class YoloProfile extends CustomProfile { + + public YoloProfile() { + super("WearBlackAllDay", true); + this.setTypeState(Finder.Type.DUNGEON, true); + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/render/Color.java b/src/main/java/kaptainwutax/seedcracker/render/Color.java new file mode 100644 index 00000000..5e81f818 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/render/Color.java @@ -0,0 +1,41 @@ +package kaptainwutax.seedcracker.render; + +public class Color { + + public static final Color WHITE = new Color(255, 255, 255); + + private final int red; + private final int green; + private final int blue; + + public Color(int red, int green, int blue) { + this.red = red; + this.green = green; + this.blue = blue; + } + + public int getRed() { + return this.red; + } + + public int getGreen() { + return this.green; + } + + public int getBlue() { + return this.blue; + } + + public float getFRed() { + return this.getRed() / 255.0F; + } + + public float getFGreen() { + return this.getGreen() / 255.0F; + } + + public float getFBlue() { + return this.getBlue() / 255.0F; + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/render/Cube.java b/src/main/java/kaptainwutax/seedcracker/render/Cube.java new file mode 100644 index 00000000..47960a76 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/render/Cube.java @@ -0,0 +1,25 @@ +package kaptainwutax.seedcracker.render; + +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3i; + +public class Cube extends Cuboid { + + public Cube() { + this(BlockPos.ORIGIN, Color.WHITE); + } + + public Cube(BlockPos pos) { + this(pos, Color.WHITE); + } + + public Cube(BlockPos pos, Color color) { + super(pos, new Vec3i(1, 1, 1), color); + } + + @Override + public BlockPos getPos() { + return this.start; + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/render/Cuboid.java b/src/main/java/kaptainwutax/seedcracker/render/Cuboid.java new file mode 100644 index 00000000..7edbe195 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/render/Cuboid.java @@ -0,0 +1,62 @@ +package kaptainwutax.seedcracker.render; + +import net.minecraft.util.math.BlockBox; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3i; + +public class Cuboid extends Renderer { + + public BlockPos start; + public Vec3i size; + + private Line[] edges = new Line[12]; + + public Cuboid() { + this(BlockPos.ORIGIN, BlockPos.ORIGIN, Color.WHITE); + } + + public Cuboid(BlockPos pos) { + this(pos, new BlockPos(1, 1, 1), Color.WHITE); + } + + public Cuboid(BlockPos start, BlockPos end, Color color) { + this(start, new Vec3i(end.getX() - start.getX(), end.getY() - start.getY(), end.getZ() - start.getZ()), color); + } + + public Cuboid(BlockBox box, Color color) { + this(new BlockPos(box.minX, box.minY, box.minZ), new BlockPos(box.maxX, box.maxY, box.maxZ), color); + } + + public Cuboid(BlockPos start, Vec3i size, Color color) { + this.start = start; + this.size = size; + this.edges[0] = new Line(toVec3d(this.start), toVec3d(this.start.add(this.size.getX(), 0, 0)), color); + this.edges[1] = new Line(toVec3d(this.start), toVec3d(this.start.add(0, this.size.getY(), 0)), color); + this.edges[2] = new Line(toVec3d(this.start), toVec3d(this.start.add(0, 0, this.size.getZ())), color); + this.edges[3] = new Line(toVec3d(this.start.add(this.size.getX(), 0, this.size.getZ())), toVec3d(this.start.add(this.size.getX(), 0, 0)), color); + this.edges[4] = new Line(toVec3d(this.start.add(this.size.getX(), 0, this.size.getZ())), toVec3d(this.start.add(this.size.getX(), this.size.getY(), this.size.getZ())), color); + this.edges[5] = new Line(toVec3d(this.start.add(this.size.getX(), 0, this.size.getZ())), toVec3d(this.start.add(0, 0, this.size.getZ())), color); + this.edges[6] = new Line(toVec3d(this.start.add(this.size.getX(), this.size.getY(), 0)), toVec3d(this.start.add(this.size.getX(), 0, 0)), color); + this.edges[7] = new Line(toVec3d(this.start.add(this.size.getX(), this.size.getY(), 0)), toVec3d(this.start.add(0, this.size.getY(), 0)), color); + this.edges[8] = new Line(toVec3d(this.start.add(this.size.getX(), this.size.getY(), 0)), toVec3d(this.start.add(this.size.getX(), this.size.getY(), this.size.getZ())), color); + this.edges[9] = new Line(toVec3d(this.start.add(0, this.size.getY(), this.size.getZ())), toVec3d(this.start.add(0, 0, this.size.getZ())), color); + this.edges[10] = new Line(toVec3d(this.start.add(0, this.size.getY(), this.size.getZ())), toVec3d(this.start.add(0, this.size.getY(), 0)), color); + this.edges[11] = new Line(toVec3d(this.start.add(0, this.size.getY(), this.size.getZ())), toVec3d(this.start.add(this.size.getX(), this.size.getY(), this.size.getZ())), color); + } + + @Override + public void render() { + if(this.start == null || this.size == null || this.edges == null)return; + + for(Line edge: this.edges) { + if(edge == null)continue; + edge.render(); + } + } + + @Override + public BlockPos getPos() { + return this.start.add(this.size.getX() / 2, this.size.getY() / 2, this.size.getZ() / 2); + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/render/Line.java b/src/main/java/kaptainwutax/seedcracker/render/Line.java new file mode 100644 index 00000000..35c5b37a --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/render/Line.java @@ -0,0 +1,71 @@ +package kaptainwutax.seedcracker.render; + +import com.mojang.blaze3d.platform.GlStateManager; +import net.minecraft.client.render.BufferBuilder; +import net.minecraft.client.render.Tessellator; +import net.minecraft.client.render.VertexFormats; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; + +public class Line extends Renderer { + + public Vec3d start; + public Vec3d end; + public Color color; + + public Line() { + this(Vec3d.ZERO, Vec3d.ZERO, Color.WHITE); + } + + public Line(Vec3d start, Vec3d end) { + this(start, end, Color.WHITE); + } + + public Line(Vec3d start, Vec3d end, Color color) { + this.start = start; + this.end = end; + this.color = color; + } + + @Override + public void render() { + if(this.start == null || this.end == null || this.color == null)return; + + Vec3d camPos = this.mc.gameRenderer.getCamera().getPos(); + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder buffer = tessellator.getBuffer(); + + //This is how thick the line is. + GlStateManager.lineWidth(2.0f); + buffer.begin(3, VertexFormats.POSITION_COLOR); + + //Put the start and end vertices in the buffer. + this.putVertex(buffer, camPos, this.start); + this.putVertex(buffer, camPos, this.end); + + //Draw it all. + tessellator.draw(); + } + + protected void putVertex(BufferBuilder buffer, Vec3d camPos, Vec3d pos) { + buffer.vertex( + pos.getX() - camPos.x, + pos.getY() - camPos.y, + pos.getZ() - camPos.z + ).color( + this.color.getFRed(), + this.color.getFGreen(), + this.color.getFBlue(), + 1.0F + ).next(); + } + + @Override + public BlockPos getPos() { + double x = (this.end.getX() - this.start.getX()) / 2 + this.start.getX(); + double y = (this.end.getY() - this.start.getY()) / 2 + this.start.getY(); + double z = (this.end.getZ() - this.start.getZ()) / 2 + this.start.getZ(); + return new BlockPos(x, y, z); + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/render/RenderQueue.java b/src/main/java/kaptainwutax/seedcracker/render/RenderQueue.java new file mode 100644 index 00000000..e993726d --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/render/RenderQueue.java @@ -0,0 +1,52 @@ +package kaptainwutax.seedcracker.render; + +import net.minecraft.client.util.math.MatrixStack; + +import java.util.*; +import java.util.function.Consumer; + +public class RenderQueue { + + private final static RenderQueue INSTANCE = new RenderQueue(); + + private Map>> typeRunnableMap = new HashMap<>(); + private MatrixStack matrixStack = null; + + public static RenderQueue get() { + return INSTANCE; + } + + public void add(String type, Consumer runnable) { + Objects.requireNonNull(type); + Objects.requireNonNull(runnable); + + if(!this.typeRunnableMap.containsKey(type)) { + this.typeRunnableMap.put(type, new ArrayList<>()); + } + + List> runnableList = this.typeRunnableMap.get(type); + runnableList.add(runnable); + } + + public void remove(String type, Consumer runnable) { + Objects.requireNonNull(type); + Objects.requireNonNull(runnable); + + if(!this.typeRunnableMap.containsKey(type)) { + return; + } + + List> runnableList = this.typeRunnableMap.get(type); + runnableList.remove(runnable); + } + + public void setTrackRender(MatrixStack matrixStack) { + this.matrixStack = matrixStack; + } + + public void onRender(String type) { + if(this.matrixStack == null || !this.typeRunnableMap.containsKey(type))return; + this.typeRunnableMap.get(type).forEach(r -> r.accept(this.matrixStack)); + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/render/Renderer.java b/src/main/java/kaptainwutax/seedcracker/render/Renderer.java new file mode 100644 index 00000000..d1c97afb --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/render/Renderer.java @@ -0,0 +1,19 @@ +package kaptainwutax.seedcracker.render; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; + +public abstract class Renderer { + + protected MinecraftClient mc = MinecraftClient.getInstance(); + + public abstract void render(); + + public abstract BlockPos getPos(); + + protected Vec3d toVec3d(BlockPos pos) { + return new Vec3d(pos.getX(), pos.getY(), pos.getZ()); + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/util/BiomeFixer.java b/src/main/java/kaptainwutax/seedcracker/util/BiomeFixer.java new file mode 100644 index 00000000..743523d2 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/util/BiomeFixer.java @@ -0,0 +1,18 @@ +package kaptainwutax.seedcracker.util; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.util.registry.BuiltinRegistries; +import net.minecraft.util.registry.Registry; + +public class BiomeFixer { + + public static kaptainwutax.biomeutils.Biome swap(net.minecraft.world.biome.Biome biome) { + return kaptainwutax.biomeutils.Biome.REGISTRY.get(MinecraftClient.getInstance().getNetworkHandler() + .getRegistryManager().get(Registry.BIOME_KEY).getRawId(biome)); + } + + public static net.minecraft.world.biome.Biome swap(kaptainwutax.biomeutils.Biome biome) { + return BuiltinRegistries.BIOME.get(biome.getId()); + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/util/Log.java b/src/main/java/kaptainwutax/seedcracker/util/Log.java new file mode 100644 index 00000000..bf7a7fac --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/util/Log.java @@ -0,0 +1,65 @@ +package kaptainwutax.seedcracker.util; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.text.*; +import net.minecraft.util.Formatting; + +import java.util.regex.Pattern; + +public class Log { + + public static void debug(String message) { + PlayerEntity player = getPlayer(); + + if(player != null) { + schedule(() -> player.sendMessage(new LiteralText(message), false)); + } + } + + public static void warn(String message) { + PlayerEntity player = getPlayer(); + + if(player != null) { + schedule(() -> player.sendMessage(new LiteralText(message).formatted(Formatting.GREEN), false)); + } + } + + public static void error(String message) { + PlayerEntity player = getPlayer(); + + if(player != null) { + schedule(() -> player.sendMessage(new LiteralText(message).formatted(Formatting.RED), false)); + } + } + + public static void printSeed(String message, long seedValue) { + String[] data = message.split(Pattern.quote("${SEED}")); + String seed = String.valueOf(seedValue); + Text text = Texts.bracketed((new LiteralText(seed)).styled(style -> style.withColor(Formatting.GREEN).withClickEvent(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, seed)).withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new TranslatableText("chat.copy.click"))).withInsertion(seed))); + + PlayerEntity player = getPlayer(); + + if(player != null) { + schedule(() -> player.sendMessage(new LiteralText(data[0]).append(text).append(new LiteralText(data[1])), false)); + } + } + public static void printDungeonInfo(String message) { + Text text = Texts.bracketed((new LiteralText(message)).styled(style -> style.withColor(Formatting.GREEN).withClickEvent(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, message)).withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new TranslatableText("chat.copy.click"))).withInsertion(message))); + + PlayerEntity player = getPlayer(); + + if(player != null) { + schedule(() -> player.sendMessage(text,false)); + } + } + + private static void schedule(Runnable runnable) { + MinecraftClient.getInstance().execute(runnable); + } + + private static PlayerEntity getPlayer() { + return MinecraftClient.getInstance().player; + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/util/PosIterator.java b/src/main/java/kaptainwutax/seedcracker/util/PosIterator.java new file mode 100644 index 00000000..9c9babd6 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/util/PosIterator.java @@ -0,0 +1,24 @@ +package kaptainwutax.seedcracker.util; + +import net.minecraft.util.math.BlockPos; + +import java.util.HashSet; +import java.util.Set; + +public class PosIterator { + + public static Set create(BlockPos start, BlockPos end) { + Set result = new HashSet<>(); + + for(int x = start.getX(); x <= end.getX(); x++) { + for(int z = start.getZ(); z <= end.getZ(); z++) { + for(int y = start.getY(); y <= end.getY(); y++) { + result.add(new BlockPos(x, y, z)); + } + } + } + + return result; + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/util/Predicates.java b/src/main/java/kaptainwutax/seedcracker/util/Predicates.java new file mode 100644 index 00000000..b58139f8 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/util/Predicates.java @@ -0,0 +1,14 @@ +package kaptainwutax.seedcracker.util; + +import java.util.function.BiPredicate; + +public class Predicates { + + public static BiPredicate EQUAL_TO = Integer::equals; + public static BiPredicate NOT_EQUAL_TO = (a, b) -> !a.equals(b); + public static BiPredicate LESS_THAN = (a, b) -> a < b; + public static BiPredicate MORE_THAN = (a, b) -> a > b; + public static BiPredicate LESS_OR_EQUAL_TO = (a, b) -> a <= b; + public static BiPredicate MORE_OR_EQUAL_TO = (a, b) -> a >= b; + +} diff --git a/src/main/resources/assets/seedcracker/icon.png b/src/main/resources/assets/seedcracker/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..047b91f2347de5cf95f23284476fddbe21ba23fe GIT binary patch literal 453 zcmV;$0XqJPP)QAFYGys`80vegN0XDFh0OXKz&i8?Le#x7{1X)R+00000NkvXXu0mjf73i~T literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/seedcracker/lang/en_us.json b/src/main/resources/assets/seedcracker/lang/en_us.json new file mode 100644 index 00000000..bb18606a --- /dev/null +++ b/src/main/resources/assets/seedcracker/lang/en_us.json @@ -0,0 +1,4 @@ +{ + "key.categories.seedcracker": "Seed Cracker", + "key.open_menu": "Open Menu" +} \ No newline at end of file diff --git a/src/main/resources/assets/seedcracker/lang/zh_cn.json b/src/main/resources/assets/seedcracker/lang/zh_cn.json new file mode 100644 index 00000000..71eb002e --- /dev/null +++ b/src/main/resources/assets/seedcracker/lang/zh_cn.json @@ -0,0 +1,4 @@ +{ + "key.categories.seedcracker": "种子破解器", + "key.open_menu": "打开菜单" +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json new file mode 100644 index 00000000..3fb4ba22 --- /dev/null +++ b/src/main/resources/fabric.mod.json @@ -0,0 +1,35 @@ +{ + "schemaVersion": 1, + "id": "seedcracker", + "version": "${version}", + + "name": "Seed Cracker", + "description": "This is an example description! Tell everyone what your mod is about!", + "authors": [ + "KaptainWutax" + ], + "contact": { + "homepage": "https://fabricmc.net/", + "sources": "https://github.com/FabricMC/fabric-example-mod" + }, + + "license": "CC0-1.0", + "icon": "assets/seecracker/icon.png", + + "environment": "client", + "entrypoints": { + "main": [ + "kaptainwutax.seedcracker.SeedCracker" + ] + }, + "mixins": [ + "seedcracker.mixins.json" + ], + + "depends": { + "fabricloader": ">=0.4.0" + }, + "suggests": { + "flamingo": "*" + } +} diff --git a/src/main/resources/seedcracker.mixins.json b/src/main/resources/seedcracker.mixins.json new file mode 100644 index 00000000..839d3fd8 --- /dev/null +++ b/src/main/resources/seedcracker.mixins.json @@ -0,0 +1,20 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "kaptainwutax.seedcracker.mixin", + "compatibilityLevel": "JAVA_8", + "mixins": [ + ], + "client": [ + "DummyProfilerMixin", + "GameRendererMixin", + "ClientPlayNetworkHandlerMixin", + "ClientWorldMixin", + "ClientPlayerEntityMixin", + "ServerChunkManagerMixin", + "DimensionTypeMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/taskBuild.bat b/taskBuild.bat new file mode 100644 index 00000000..646a0cb1 --- /dev/null +++ b/taskBuild.bat @@ -0,0 +1 @@ +gradlew build \ No newline at end of file diff --git a/taskSetup.bat b/taskSetup.bat new file mode 100644 index 00000000..00c514c7 --- /dev/null +++ b/taskSetup.bat @@ -0,0 +1 @@ +gradlew genSources idea \ No newline at end of file