├── .gitattributes ├── .gitignore ├── LICENSE.txt ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── main ├── java └── com │ └── telepathicgrunt │ └── structure_tutorial │ ├── STStructurePlacements.java │ ├── STStructures.java │ ├── StructureTutorialMain.java │ ├── structureplacement │ └── DistanceBasedStructurePlacement.java │ ├── structures │ ├── EndIslandStructures.java │ ├── OceanStructures.java │ └── SkyStructures.java │ └── utilities │ └── FilterHolderSet.java └── resources ├── META-INF └── neoforge.mods.toml ├── data ├── minecraft │ └── tags │ │ └── worldgen │ │ └── structure │ │ └── on_ocean_explorer_maps.json └── structure_tutorial │ ├── structure │ ├── end_phantom_balloon.nbt │ ├── run_down_house_left_side.nbt │ ├── run_down_house_right_side.nbt │ ├── run_down_house_right_side_golden.nbt │ ├── sea_boat.nbt │ └── sky_fan.nbt │ ├── tags │ └── worldgen │ │ └── biome │ │ ├── exclude_structure │ │ └── code_structure_sea_boat_biomes.json │ │ └── has_structure │ │ ├── code_structure_end_phantom_balloon_biomes.json │ │ ├── code_structure_sea_boat_biomes.json │ │ ├── code_structure_sky_fan_biomes.json │ │ └── json_only_house_biomes.json │ └── worldgen │ ├── processor_list │ └── randomize_stonebrick.json │ ├── structure │ ├── code_structure_end_phantom_balloon.json │ ├── code_structure_sea_boat.json │ ├── code_structure_sky_fan.json │ └── json_only_house.json │ ├── structure_set │ ├── code_structure_end_phantom_balloon.json │ ├── code_structure_sea_boat.json │ ├── code_structure_sky_fan.json │ └── json_only_house.json │ └── template_pool │ ├── end_phantom_balloon.json │ ├── run_down_house │ ├── side_pool.json │ └── start_pool.json │ ├── sea_boat.json │ └── sky_fan.json └── pack.mcmeta /.gitattributes: -------------------------------------------------------------------------------- 1 | *.json linguist-language=jsonc -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # eclipse 2 | bin 3 | *.launch 4 | .settings 5 | .metadata 6 | .classpath 7 | .project 8 | 9 | # idea 10 | out 11 | *.ipr 12 | *.iws 13 | *.iml 14 | .idea 15 | 16 | # gradle 17 | build 18 | .gradle 19 | 20 | # other 21 | eclipse 22 | run 23 | runs 24 | 25 | # Files from Forge MDK 26 | forge*changelog.txt 27 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 TelepathicGrunt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Structure Tutorial Mod (NeoForge) 2 | **How to register and generate jigsaw structures in 1.21.1+ Minecraft NeoForge using nbt files! In fact, you can actually make structures using only json files in 1.18.2+ MC!** 3 | 4 | **Change the Github branch to see other versions of this tutorial including for Fabric!** 5 | 6 | This very small NeoForge mod is full of comments that will help you understand what a lot of the json files and what needs to be done to get your structure to generate. In all, minimum number of files for a structure is 4 json files and 1 nbt file. The json_only_house structure is made using just json files while the other 3 structures are mostly json files but has custom java classes to do extra behavior for it. 7 | --- 8 | 9 | There are 4 structures in this tutorial mod. Each one focusing on different setups you may want. 10 | 11 | - JSON Only House 12 | - This structure uses only JSON files to be made. No code needed. 13 | - Uses Jigsaw blocks in the NBT files and template_pool JSON files to combine two NBT pieces together. A 2 piece structure. 14 | - Has an entity saved into NBT with persistence required. See the structure in-world for what the entity is and signs explaining it. 15 | - Has a chest with a Loot Table saved to it, so it has random loot on first opening. See the structure in-world for signs explaining it. 16 | - Overrides the natural biome creature and monster spawns with its own. See the worldgen/structure file for this. 17 | 18 | - Sky Fan 19 | - This structure uses code to ensure it never spawns above land that is higher than y = 150. See worldgen/structure and SkyStructures.java for this. 20 | - Has blocks randomized throughout the structure by using a processor list. See worldgen/template_pool and worldgen/processor_list for this. 21 | - Has 4 entity saved into NBT with persistence required. 22 | - Overrides the natural biome monster spawns with its own. See the worldgen/structure file for this. 23 | 24 | - Sea Boat 25 | - This structure uses code to allow using 2 biome tags. 1 to say what biomes to spawn in and another for biome to ignore and not spawn in. See worldgen/structure and OceanStructures.java for this. 26 | - This structure also checks to makes sure spawn location has water. See worldgen/structure and OceanStructures.java for this. 27 | 28 | - End Phantom Balloon 29 | - This structure uses code to ensure it only spawns above the large end islands. See worldgen/structure and SkyStructures.java for this. 30 | - Uses a custom structure placement so it only spawns 1000 blocks or more from world center. See worldgen/structure_set and DistanceBasedStructurePlacement.java 31 | 32 | --- 33 | 34 | All vanilla worldgen JSON and NBT files can be found here. Older Minecraft versions are under the commit history: https://github.com/misode/mcmeta/tree/data/data/minecraft 35 | 36 | Some other tutorials you can check out for extra info that might be easier to understand: 37 | 38 | - https://minecraft.wiki/w/Tutorials/Custom_structures 39 | 40 | - https://www.planetminecraft.com/blog/custom-structure-gen-documentation/ 41 | 42 | - https://gist.github.com/GentlemanRevvnar/98a8f191f46d28f63592672022c41497 43 | 44 | If you don't know how to make a nbt file for structures, it's actually fairly simple and you can do it all inside minecraft itself! Here is a video on how to make and save a structure to nbt using structure blocks. Here's a short video on how Structure Blocks work: 45 | >https://www.youtube.com/watch?v=umhuRXinD3o 46 | 47 | If your structure is only a **single piece, then you do NOT need any Jigsaw Blocks** and can just use 1 pool file. If you want your jigsaw structure to have more than 1 piece, you will need to setup and save Jigsaw blocks into your structure's nbt files. The Jigsaw blocks acts as connectors between the nbt pieces and pool files. (Here's two videos about using Jigsaw Blocks in structures! The first one is very long but extremely detailed.): 48 | >https://www.youtube.com/watch?v=5a4DAkWW3JQ 49 | 50 | Handy image for a shorthand way of how Jigsaw Blocks and Pools work together: 51 | >![Jigsaw Cheatsheet](https://github.com/TelepathicGrunt/StructureTutorialMod/assets/40846040/dc5eb44d-ddbf-4302-a4c9-e544a53f7981) 52 | 53 | 54 | This picture shows how structure jigsaw pieces are only valid if the child piece fits entirely within the parent or entirely outside the parent piece. Partial intersections will prevent the piece from spawning. 55 | 56 | 57 | 58 | And here, if a piece fails to spawn, it will go to the fallback pool, pick a new piece from the fallback pool, and try to spawn that piece if there is room for it. Great for closing off the ends of hallways! 59 | 60 | 61 | 62 | Once saved, the structure nbt file is stored in that world's save folder within the generated folder inside. Grab those files as you'll need to put it under your mod's resource folder inside data/mod_id/structures. NOTICE: This the data folder and not the asset folder inside resource! Then make a JSON file and put it in data/mod_id/worldgen/template_pool folder. Take a look at this tutorial's start_pool.json file for how to setup the JSON file itself. 63 | >![Image of the folder layout for Structure Tutorial Mod which shows the structure nbt files are inside data.structure_tutorial.structures which is inside src/main/resources](https://github.com/TelepathicGrunt/StructureTutorialMod/assets/40846040/182e07fb-8d91-4ea2-8152-97c5ad64ff41) 64 | 65 | 66 | 67 | Now you're ready to begin adding the structure to your mod! Take a look at StructureTutorialMain, start reading the comments, and follow the json/methods/classes. Don't just copy the code quickly or else you will get confuse. Take your time and try to understand how it all works before attempting to register and generate your structure in your own mod. (Also, check out the house structure itself in the world! There's some info on signs in the structure itself about nbt files) 68 | 69 | Also, if you get stuck on the template_pool json, here's a datapack of the entire vanilla worldgen. Including vanilla's template_pools. Use this if you want to see how vanilla setup their pools! https://github.com/TelepathicGrunt/StructureTutorialMod/releases/tag/0.0.0 70 | 71 | Good luck and I hope this helps! 72 | 73 | You can contact me through issue reports here or on discord. My Discord is TelepathicGrunt#7397 and my channel is: https://discord.gg/K8qRev3yKZ 74 | 75 | 76 | ------------------ 77 | 78 | My Patreon if you wish to support me! 79 | https://www.patreon.com/telepathicgrunt 80 | 81 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java-library' 3 | id 'eclipse' 4 | id 'idea' 5 | id 'maven-publish' 6 | id 'net.neoforged.gradle.userdev' version '7.0.142' 7 | } 8 | 9 | version = mod_version 10 | group = mod_group_id 11 | 12 | repositories { 13 | mavenLocal() 14 | } 15 | 16 | base { 17 | archivesName = mod_id 18 | } 19 | 20 | java.toolchain.languageVersion = JavaLanguageVersion.of(21) 21 | 22 | runs { 23 | configureEach { 24 | modSource project.sourceSets.main 25 | } 26 | 27 | client { 28 | } 29 | 30 | server { 31 | programArgument '--nogui' 32 | } 33 | } 34 | 35 | configurations { 36 | runtimeClasspath.extendsFrom localRuntime 37 | } 38 | 39 | dependencies { 40 | implementation "net.neoforged:neoforge:${neo_version}" 41 | } 42 | 43 | tasks.withType(JavaCompile).configureEach { 44 | options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation 45 | } 46 | 47 | tasks.withType(ProcessResources).configureEach { 48 | var replaceProperties = [ 49 | minecraft_version : minecraft_version, 50 | minecraft_version_range: minecraft_version_range, 51 | neo_version : neo_version, 52 | neo_version_range : neo_version_range, 53 | loader_version_range : loader_version_range, 54 | mod_id : mod_id, 55 | mod_name : mod_name, 56 | mod_license : mod_license, 57 | mod_version : mod_version, 58 | mod_authors : mod_authors, 59 | mod_description : mod_description, 60 | mod_credits : mod_credits 61 | ] 62 | inputs.properties replaceProperties 63 | 64 | filesMatching(['META-INF/neoforge.mods.toml']) { 65 | expand replaceProperties 66 | } 67 | } 68 | 69 | idea { 70 | module { 71 | downloadSources = true 72 | downloadJavadoc = true 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Sets default memory used for gradle commands. Can be overridden by user or command line properties. 2 | # This is required to provide enough memory for the Minecraft decompilation process. 3 | org.gradle.jvmargs=-Xmx3G 4 | org.gradle.daemon=false 5 | 6 | minecraft_version=1.21 7 | minecraft_version_range=[1.21,1.22) 8 | neo_version=21.0.1-beta 9 | neo_version_range=[21.0.0-beta,) 10 | loader_version_range=[2,) 11 | 12 | mod_id=structure_tutorial 13 | mod_name=Structure Tutorial Mod 14 | mod_version=4.1.0 15 | mod_license=MIT License 16 | mod_group_id=com.telepathicgrunt.structure_tutorial_mod 17 | mod_authors=TelepathicGrunt 18 | mod_description=A short mod example on how to register structures and generate them in any biome using structure nbt files. 19 | mod_credits=Special thanks to nhwhite3118 for helping out! -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TelepathicGrunt/StructureTutorialMod/10334b0c90f0dce3d0b7aecf6087cd6b7ea1d0c9/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 147 | # shellcheck disable=SC3045 148 | MAX_FD=$( ulimit -H -n ) || 149 | warn "Could not query maximum file descriptor limit" 150 | esac 151 | case $MAX_FD in #( 152 | '' | soft) :;; #( 153 | *) 154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 155 | # shellcheck disable=SC3045 156 | ulimit -n "$MAX_FD" || 157 | warn "Could not set maximum file descriptor limit to $MAX_FD" 158 | esac 159 | fi 160 | 161 | # Collect all arguments for the java command, stacking in reverse order: 162 | # * args from the command line 163 | # * the main class name 164 | # * -classpath 165 | # * -D...appname settings 166 | # * --module-path (only if needed) 167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 168 | 169 | # For Cygwin or MSYS, switch paths to Windows format before running java 170 | if "$cygwin" || "$msys" ; then 171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 173 | 174 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 175 | 176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 177 | for arg do 178 | if 179 | case $arg in #( 180 | -*) false ;; # don't mess with options #( 181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 182 | [ -e "$t" ] ;; #( 183 | *) false ;; 184 | esac 185 | then 186 | arg=$( cygpath --path --ignore --mixed "$arg" ) 187 | fi 188 | # Roll the args list around exactly as many times as the number of 189 | # args, so each arg winds up back in the position where it started, but 190 | # possibly modified. 191 | # 192 | # NB: a `for` loop captures its iteration list before it begins, so 193 | # changing the positional parameters here affects neither the number of 194 | # iterations, nor the values presented in `arg`. 195 | shift # remove old arg 196 | set -- "$@" "$arg" # push replacement arg 197 | done 198 | fi 199 | 200 | # Collect all arguments for the java command; 201 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 202 | # shell script including quotes and variable substitutions, so put them in 203 | # double quotes to make sure that they get re-expanded; and 204 | # * put everything else in single quotes, so that it's not re-expanded. 205 | 206 | set -- \ 207 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 208 | -classpath "$CLASSPATH" \ 209 | org.gradle.wrapper.GradleWrapperMain \ 210 | "$@" 211 | 212 | # Stop when "xargs" is not available. 213 | if ! command -v xargs >/dev/null 2>&1 214 | then 215 | die "xargs is not available" 216 | fi 217 | 218 | # Use "xargs" to parse quoted args. 219 | # 220 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 221 | # 222 | # In Bash we could simply go: 223 | # 224 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 225 | # set -- "${ARGS[@]}" "$@" 226 | # 227 | # but POSIX shell has neither arrays nor command substitution, so instead we 228 | # post-process each arg (as a line of input to sed) to backslash-escape any 229 | # character that might be a shell metacharacter, then use eval to reverse 230 | # that process (while maintaining the separation between arguments), and wrap 231 | # the whole thing up as a single "set" statement. 232 | # 233 | # This will of course break if any of these variables contains a newline or 234 | # an unmatched quote. 235 | # 236 | 237 | eval "set -- $( 238 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 239 | xargs -n1 | 240 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 241 | tr '\n' ' ' 242 | )" '"$@"' 243 | 244 | exec "$JAVACMD" "$@" 245 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | mavenLocal() 4 | gradlePluginPortal() 5 | maven { url = 'https://maven.neoforged.net/releases' } 6 | } 7 | } 8 | 9 | plugins { 10 | id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0' 11 | } -------------------------------------------------------------------------------- /src/main/java/com/telepathicgrunt/structure_tutorial/STStructurePlacements.java: -------------------------------------------------------------------------------- 1 | package com.telepathicgrunt.structure_tutorial; 2 | 3 | import com.mojang.serialization.MapCodec; 4 | import com.telepathicgrunt.structure_tutorial.structureplacement.DistanceBasedStructurePlacement; 5 | import net.minecraft.core.registries.Registries; 6 | import net.minecraft.world.level.levelgen.structure.placement.StructurePlacement; 7 | import net.minecraft.world.level.levelgen.structure.placement.StructurePlacementType; 8 | import net.neoforged.neoforge.registries.DeferredHolder; 9 | import net.neoforged.neoforge.registries.DeferredRegister; 10 | 11 | public class STStructurePlacements { 12 | 13 | /** 14 | * We are using the Deferred Registry system to register our structure as this is the preferred way on NeoForge. 15 | * This will handle registering the structure placement type for us at the correct time so we don't have to handle it ourselves. 16 | */ 17 | public static final DeferredRegister> DEFERRED_REGISTRY_STRUCTURE_PLACEMENT_TYPE = DeferredRegister.create(Registries.STRUCTURE_PLACEMENT, StructureTutorialMain.MODID); 18 | 19 | /** 20 | * Registers the structure placement type itself and sets what its path is. In this case, 21 | * this base structure will have the resourcelocation of structure_tutorial:distance_based_structure_placement. 22 | */ 23 | public static final DeferredHolder, StructurePlacementType> DISTANCE_BASED_STRUCTURE_PLACEMENT = DEFERRED_REGISTRY_STRUCTURE_PLACEMENT_TYPE.register("distance_based_structure_placement", () -> explicitStructureTypeTyping(DistanceBasedStructurePlacement.CODEC)); 24 | 25 | /** 26 | * Originally, I had a double lambda ()->()-> for the RegistryObject line above, but it turns out that 27 | * some IDEs cannot resolve the typing correctly. This method explicitly states what the return type 28 | * is so that the IDE can put it into the DeferredRegistry properly. 29 | */ 30 | private static StructurePlacementType explicitStructureTypeTyping(MapCodec structurePlacementTypeCodec) { 31 | return () -> structurePlacementTypeCodec; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/telepathicgrunt/structure_tutorial/STStructures.java: -------------------------------------------------------------------------------- 1 | package com.telepathicgrunt.structure_tutorial; 2 | 3 | import com.mojang.serialization.MapCodec; 4 | import com.telepathicgrunt.structure_tutorial.structures.EndIslandStructures; 5 | import com.telepathicgrunt.structure_tutorial.structures.OceanStructures; 6 | import com.telepathicgrunt.structure_tutorial.structures.SkyStructures; 7 | import net.minecraft.core.registries.Registries; 8 | import net.minecraft.world.level.levelgen.structure.Structure; 9 | import net.minecraft.world.level.levelgen.structure.StructureType; 10 | import net.neoforged.neoforge.registries.DeferredHolder; 11 | import net.neoforged.neoforge.registries.DeferredRegister; 12 | 13 | public class STStructures { 14 | 15 | /** 16 | * We are using the Deferred Registry system to register our structure as this is the preferred way on NeoForge. 17 | * This will handle registering the base structure for us at the correct time so we don't have to handle it ourselves. 18 | */ 19 | public static final DeferredRegister> DEFERRED_REGISTRY_STRUCTURE = DeferredRegister.create(Registries.STRUCTURE_TYPE, StructureTutorialMain.MODID); 20 | 21 | /** 22 | * Registers the base structure itself and sets what its path is. In this case, 23 | * this base structure will have the resourcelocation of structure_tutorial:sky_structures. 24 | */ 25 | public static final DeferredHolder, StructureType> SKY_STRUCTURES = DEFERRED_REGISTRY_STRUCTURE.register("sky_structures", () -> explicitStructureTypeTyping(SkyStructures.CODEC)); 26 | public static final DeferredHolder, StructureType> OCEAN_STRUCTURES = DEFERRED_REGISTRY_STRUCTURE.register("ocean_structures", () -> explicitStructureTypeTyping(OceanStructures.CODEC)); 27 | public static final DeferredHolder, StructureType> END_ISLAND_STRUCTURES = DEFERRED_REGISTRY_STRUCTURE.register("end_island_structures", () -> explicitStructureTypeTyping(EndIslandStructures.CODEC)); 28 | 29 | /** 30 | * Originally, I had a double lambda ()->()-> for the RegistryObject line above, but it turns out that 31 | * some IDEs cannot resolve the typing correctly. This method explicitly states what the return type 32 | * is so that the IDE can put it into the DeferredRegistry properly. 33 | */ 34 | private static StructureType explicitStructureTypeTyping(MapCodec structureCodec) { 35 | return () -> structureCodec; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/telepathicgrunt/structure_tutorial/StructureTutorialMain.java: -------------------------------------------------------------------------------- 1 | package com.telepathicgrunt.structure_tutorial; 2 | 3 | import net.neoforged.bus.api.IEventBus; 4 | import net.neoforged.fml.common.Mod; 5 | import org.apache.logging.log4j.LogManager; 6 | import org.apache.logging.log4j.Logger; 7 | 8 | @Mod(StructureTutorialMain.MODID) 9 | public class StructureTutorialMain { 10 | 11 | public static final Logger LOGGER = LogManager.getLogger(); 12 | public static final String MODID = "structure_tutorial"; 13 | 14 | public StructureTutorialMain(IEventBus modEventBus) { 15 | // For registration and init stuff. 16 | STStructures.DEFERRED_REGISTRY_STRUCTURE.register(modEventBus); 17 | STStructurePlacements.DEFERRED_REGISTRY_STRUCTURE_PLACEMENT_TYPE.register(modEventBus); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/telepathicgrunt/structure_tutorial/structureplacement/DistanceBasedStructurePlacement.java: -------------------------------------------------------------------------------- 1 | package com.telepathicgrunt.structure_tutorial.structureplacement; 2 | 3 | import com.mojang.serialization.Codec; 4 | import com.mojang.serialization.MapCodec; 5 | import com.mojang.serialization.codecs.RecordCodecBuilder; 6 | import com.telepathicgrunt.structure_tutorial.STStructurePlacements; 7 | import net.minecraft.core.Vec3i; 8 | import net.minecraft.util.ExtraCodecs; 9 | import net.minecraft.world.level.ChunkPos; 10 | import net.minecraft.world.level.chunk.ChunkGeneratorStructureState; 11 | import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement; 12 | import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadType; 13 | import net.minecraft.world.level.levelgen.structure.placement.StructurePlacement; 14 | import net.minecraft.world.level.levelgen.structure.placement.StructurePlacementType; 15 | 16 | import java.util.Optional; 17 | 18 | public class DistanceBasedStructurePlacement extends RandomSpreadStructurePlacement { 19 | 20 | // Special codec where we tacked on a "min_distance_from_world_origin" field so 21 | // we can now have structures spawn based on distance from world center. 22 | public static final MapCodec CODEC = RecordCodecBuilder.mapCodec((instance) -> instance.group( 23 | Vec3i.offsetCodec(16).optionalFieldOf("locate_offset", Vec3i.ZERO).forGetter(DistanceBasedStructurePlacement::locateOffset), 24 | StructurePlacement.FrequencyReductionMethod.CODEC.optionalFieldOf("frequency_reduction_method", StructurePlacement.FrequencyReductionMethod.DEFAULT).forGetter(DistanceBasedStructurePlacement::frequencyReductionMethod), 25 | Codec.floatRange(0.0F, 1.0F).optionalFieldOf("frequency", 1.0F).forGetter(DistanceBasedStructurePlacement::frequency), 26 | ExtraCodecs.NON_NEGATIVE_INT.fieldOf("salt").forGetter(DistanceBasedStructurePlacement::salt), 27 | StructurePlacement.ExclusionZone.CODEC.optionalFieldOf("exclusion_zone").forGetter(DistanceBasedStructurePlacement::exclusionZone), 28 | Codec.intRange(0, Integer.MAX_VALUE).fieldOf("spacing").forGetter(DistanceBasedStructurePlacement::spacing), 29 | Codec.intRange(0, Integer.MAX_VALUE).fieldOf("separation").forGetter(DistanceBasedStructurePlacement::separation), 30 | RandomSpreadType.CODEC.optionalFieldOf("spread_type", RandomSpreadType.LINEAR).forGetter(DistanceBasedStructurePlacement::spreadType), 31 | Codec.intRange(0, Integer.MAX_VALUE).optionalFieldOf("min_distance_from_world_origin").forGetter(DistanceBasedStructurePlacement::minDistanceFromWorldOrigin) 32 | ).apply(instance, instance.stable(DistanceBasedStructurePlacement::new))); 33 | 34 | private final Optional minDistanceFromWorldOrigin; 35 | 36 | public DistanceBasedStructurePlacement(Vec3i locationOffset, 37 | StructurePlacement.FrequencyReductionMethod frequencyReductionMethod, 38 | float frequency, 39 | int salt, 40 | Optional exclusionZone, 41 | int spacing, 42 | int separation, 43 | RandomSpreadType spreadType, 44 | Optional minDistanceFromWorldOrigin 45 | ) { 46 | super(locationOffset, frequencyReductionMethod, frequency, salt, exclusionZone, spacing, separation, spreadType); 47 | this.minDistanceFromWorldOrigin = minDistanceFromWorldOrigin; 48 | 49 | // Helpful validation to ensure that spacing value is always greater than separation value 50 | if (spacing <= separation) { 51 | throw new RuntimeException(""" 52 | Spacing cannot be less or equal to separation. 53 | Please correct this error as there's no way to spawn this structure properly 54 | Spacing: %s 55 | Separation: %s. 56 | """.formatted(spacing, separation)); 57 | } 58 | } 59 | 60 | public Optional minDistanceFromWorldOrigin() { 61 | return this.minDistanceFromWorldOrigin; 62 | } 63 | 64 | // Override this method to add coordinate checking. 65 | // The x and z here is in chunk positions. 66 | // What we do is we check if the structure is too close to world center and if so, return false. 67 | // Otherwise, if far enough away, run the normal structure position choosing code. 68 | // When this returns true, the structure's type class will be called to see if the structure layout can be made. 69 | @Override 70 | protected boolean isPlacementChunk(ChunkGeneratorStructureState chunkGeneratorStructureState, int x, int z) { 71 | if (minDistanceFromWorldOrigin.isPresent()) { 72 | // Convert chunk position to block position. 73 | long xBlockPos = x * 16L; 74 | long zBlockPos = z * 16L; 75 | 76 | // Simple fast distance check without needing to do a square root. The threshold is circular around world origin. 77 | if ((xBlockPos * xBlockPos) + (zBlockPos * zBlockPos) < (((long) minDistanceFromWorldOrigin.get()) * minDistanceFromWorldOrigin.get())) { 78 | return false; 79 | } 80 | } 81 | 82 | ChunkPos chunkpos = this.getPotentialStructureChunk(chunkGeneratorStructureState.getLevelSeed(), x, z); 83 | return chunkpos.x == x && chunkpos.z == z; 84 | } 85 | 86 | @Override 87 | public StructurePlacementType type() { 88 | return STStructurePlacements.DISTANCE_BASED_STRUCTURE_PLACEMENT.get(); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/com/telepathicgrunt/structure_tutorial/structures/EndIslandStructures.java: -------------------------------------------------------------------------------- 1 | package com.telepathicgrunt.structure_tutorial.structures; 2 | 3 | import com.mojang.serialization.Codec; 4 | import com.mojang.serialization.MapCodec; 5 | import com.mojang.serialization.codecs.RecordCodecBuilder; 6 | import com.telepathicgrunt.structure_tutorial.STStructures; 7 | import net.minecraft.core.BlockPos; 8 | import net.minecraft.core.Holder; 9 | import net.minecraft.resources.ResourceLocation; 10 | import net.minecraft.world.level.ChunkPos; 11 | import net.minecraft.world.level.levelgen.Heightmap; 12 | import net.minecraft.world.level.levelgen.WorldGenerationContext; 13 | import net.minecraft.world.level.levelgen.heightproviders.HeightProvider; 14 | import net.minecraft.world.level.levelgen.structure.Structure; 15 | import net.minecraft.world.level.levelgen.structure.StructureType; 16 | import net.minecraft.world.level.levelgen.structure.pools.DimensionPadding; 17 | import net.minecraft.world.level.levelgen.structure.pools.JigsawPlacement; 18 | import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool; 19 | import net.minecraft.world.level.levelgen.structure.pools.alias.PoolAliasLookup; 20 | import net.minecraft.world.level.levelgen.structure.structures.JigsawStructure; 21 | import net.minecraft.world.level.levelgen.structure.templatesystem.LiquidSettings; 22 | 23 | import java.util.Optional; 24 | 25 | public class EndIslandStructures extends Structure { 26 | 27 | // A custom codec that changes the size limit for our code_structure_end_phantom_balloon.json's config to not be capped at 7. 28 | // With this, we can have a structure with a size limit up to 30 if we want to have extremely long branches of pieces in the structure. 29 | public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> 30 | instance.group(EndIslandStructures.settingsCodec(instance), 31 | StructureTemplatePool.CODEC.fieldOf("start_pool").forGetter(structure -> structure.startPool), 32 | ResourceLocation.CODEC.optionalFieldOf("start_jigsaw_name").forGetter(structure -> structure.startJigsawName), 33 | Codec.intRange(0, 30).fieldOf("size").forGetter(structure -> structure.size), 34 | HeightProvider.CODEC.fieldOf("start_height").forGetter(structure -> structure.startHeight), 35 | Heightmap.Types.CODEC.optionalFieldOf("project_start_to_heightmap").forGetter(structure -> structure.projectStartToHeightmap), 36 | Codec.intRange(1, 128).fieldOf("max_distance_from_center").forGetter(structure -> structure.maxDistanceFromCenter), 37 | DimensionPadding.CODEC.optionalFieldOf("dimension_padding", JigsawStructure.DEFAULT_DIMENSION_PADDING).forGetter(structure -> structure.dimensionPadding), 38 | LiquidSettings.CODEC.optionalFieldOf("liquid_settings", JigsawStructure.DEFAULT_LIQUID_SETTINGS).forGetter(structure -> structure.liquidSettings) 39 | ).apply(instance, EndIslandStructures::new)); 40 | 41 | private final Holder startPool; 42 | private final Optional startJigsawName; 43 | private final int size; 44 | private final HeightProvider startHeight; 45 | private final Optional projectStartToHeightmap; 46 | private final int maxDistanceFromCenter; 47 | private final DimensionPadding dimensionPadding; 48 | private final LiquidSettings liquidSettings; 49 | 50 | public EndIslandStructures(StructureSettings config, 51 | Holder startPool, 52 | Optional startJigsawName, 53 | int size, 54 | HeightProvider startHeight, 55 | Optional projectStartToHeightmap, 56 | int maxDistanceFromCenter, 57 | DimensionPadding dimensionPadding, 58 | LiquidSettings liquidSettings) 59 | { 60 | super(config); 61 | this.startPool = startPool; 62 | this.startJigsawName = startJigsawName; 63 | this.size = size; 64 | this.startHeight = startHeight; 65 | this.projectStartToHeightmap = projectStartToHeightmap; 66 | this.maxDistanceFromCenter = maxDistanceFromCenter; 67 | this.dimensionPadding = dimensionPadding; 68 | this.liquidSettings = liquidSettings; 69 | } 70 | 71 | /* 72 | * This is where extra checks can be done to determine if the structure can spawn here. 73 | * This only needs to be overridden if you're adding additional spawn conditions. 74 | * 75 | * Fun fact, if you set your structure separation/spacing to be 0/1, you can use 76 | * extraSpawningChecks to return true only if certain chunk coordinates are passed in 77 | * which allows you to spawn structures only at certain coordinates in the world. 78 | * 79 | * Basically, this method is used for determining if the land is at a suitable height, 80 | * if certain other structures are too close or not, or some other restrictive condition. 81 | * 82 | * For example, Pillager Outposts added a check to make sure it cannot spawn within 10 chunk of a Village. 83 | * (Bedrock Edition seems to not have the same check) 84 | * 85 | * If you are doing Nether structures, you'll probably want to spawn your structure on top of ledges. 86 | * Best way to do that is to use getBaseColumn to grab a column of blocks at the structure's x/z position. 87 | * Then loop through it and look for land with air above it and set blockpos's Y value to it. 88 | * Make sure to set the final boolean in JigsawPlacement.addPieces to false so 89 | * that the structure spawns at blockpos's y value instead of placing the structure on the Bedrock roof! 90 | * 91 | * Also, please for the love of god, do not do dimension checking here. 92 | * If you do and another mod's dimension is trying to spawn your structure, 93 | * the locate command will make minecraft hang forever and break the game. 94 | * Use the biome tags for where to spawn the structure and users can datapack 95 | * it to spawn in specific biomes that aren't in the dimension they don't like if they wish. 96 | */ 97 | private static boolean extraSpawningChecks(GenerationContext context) { 98 | // Grabs the chunk position we are at 99 | ChunkPos chunkpos = context.chunkPos(); 100 | 101 | // Checks to make sure our structure only spawns where the large end islands are and not over the void in the End. 102 | return context.chunkGenerator().getFirstOccupiedHeight( 103 | chunkpos.getMinBlockX(), 104 | chunkpos.getMinBlockZ(), 105 | Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, 106 | context.heightAccessor(), 107 | context.randomState()) > context.chunkGenerator().getMinY(); 108 | } 109 | 110 | @Override 111 | public Optional findGenerationPoint(GenerationContext context) { 112 | 113 | // Check if the spot is valid for our structure. This is just as another method for cleanness. 114 | // Returning an empty optional tells the game to skip this spot as it will not generate the structure. 115 | if (!EndIslandStructures.extraSpawningChecks(context)) { 116 | return Optional.empty(); 117 | } 118 | 119 | // Set's our spawning blockpos's y offset. 120 | int startY = this.startHeight.sample(context.random(), new WorldGenerationContext(context.chunkGenerator(), context.heightAccessor())); 121 | 122 | // Turns the chunk coordinates into actual coordinates we can use. (Gets corner of that chunk) 123 | ChunkPos chunkPos = context.chunkPos(); 124 | BlockPos blockPos = new BlockPos(chunkPos.getMinBlockX(), startY, chunkPos.getMinBlockZ()); 125 | 126 | Optional structurePiecesGenerator = 127 | JigsawPlacement.addPieces( 128 | context, // Used for JigsawPlacement to get all the proper behaviors done. 129 | this.startPool, // The starting pool to use to create the structure layout from 130 | this.startJigsawName, // Can be used to only spawn from one Jigsaw block. But we don't need to worry about this. 131 | this.size, // How deep a branch of pieces can go away from center piece. (5 means branches cannot be longer than 5 pieces from center piece) 132 | blockPos, // Where to spawn the structure. 133 | false, // "useExpansionHack" This is for legacy villages to generate properly. You should keep this false always. 134 | this.projectStartToHeightmap, // Adds the terrain height's y value to the passed in blockpos's y value. (This uses WORLD_SURFACE_WG heightmap which stops at top water too) 135 | // Here at projectStartToHeightmap, start_height's y value is 20 which means the structure spawn 20 blocks above terrain height if start_height and project_start_to_heightmap is defined in structure JSON. 136 | // Set projectStartToHeightmap to be empty optional for structure to be place only at the passed in blockpos's Y value instead. 137 | // Definitely keep this an empty optional when placing structures in the nether as otherwise, heightmap placing will put the structure on the Bedrock roof. 138 | this.maxDistanceFromCenter, // Maximum limit for how far pieces can spawn from center. You cannot set this bigger than 128 or else pieces gets cutoff. 139 | PoolAliasLookup.EMPTY, // Optional thing that allows swapping a template pool with another per structure json instance. We don't need this but see vanilla JigsawStructure class for how to wire it up if you want it. 140 | this.dimensionPadding, // Optional thing to prevent generating too close to the bottom or top of the dimension. 141 | this.liquidSettings); // Optional thing to control whether the structure will be waterlogged when replacing pre-existing water in the world. 142 | 143 | /* 144 | * Note, you are always free to make your own JigsawPlacement class and implementation of how the structure 145 | * should generate. It is tricky but extremely powerful if you are doing something that vanilla's jigsaw system cannot do. 146 | * Such as for example, forcing 3 pieces to always spawn every time, limiting how often a piece spawns, or remove the intersection limitation of pieces. 147 | */ 148 | 149 | // Return the pieces generator that is now set up so that the game runs it when it needs to create the layout of structure pieces. 150 | return structurePiecesGenerator; 151 | } 152 | 153 | @Override 154 | public StructureType type() { 155 | return STStructures.END_ISLAND_STRUCTURES.get(); // Helps the game know how to turn this structure back to json to save to chunks 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/main/java/com/telepathicgrunt/structure_tutorial/structures/OceanStructures.java: -------------------------------------------------------------------------------- 1 | package com.telepathicgrunt.structure_tutorial.structures; 2 | 3 | import com.mojang.serialization.Codec; 4 | import com.mojang.serialization.MapCodec; 5 | import com.mojang.serialization.codecs.RecordCodecBuilder; 6 | import com.telepathicgrunt.structure_tutorial.STStructures; 7 | import com.telepathicgrunt.structure_tutorial.utilities.FilterHolderSet; 8 | import net.minecraft.core.BlockPos; 9 | import net.minecraft.core.Holder; 10 | import net.minecraft.core.HolderSet; 11 | import net.minecraft.core.registries.Registries; 12 | import net.minecraft.resources.ResourceLocation; 13 | import net.minecraft.tags.FluidTags; 14 | import net.minecraft.util.StringRepresentable; 15 | import net.minecraft.world.entity.MobCategory; 16 | import net.minecraft.world.level.ChunkPos; 17 | import net.minecraft.world.level.NoiseColumn; 18 | import net.minecraft.world.level.biome.Biome; 19 | import net.minecraft.world.level.block.state.BlockState; 20 | import net.minecraft.world.level.levelgen.GenerationStep; 21 | import net.minecraft.world.level.levelgen.Heightmap; 22 | import net.minecraft.world.level.levelgen.WorldGenerationContext; 23 | import net.minecraft.world.level.levelgen.heightproviders.HeightProvider; 24 | import net.minecraft.world.level.levelgen.structure.Structure; 25 | import net.minecraft.world.level.levelgen.structure.StructureSpawnOverride; 26 | import net.minecraft.world.level.levelgen.structure.StructureType; 27 | import net.minecraft.world.level.levelgen.structure.TerrainAdjustment; 28 | import net.minecraft.world.level.levelgen.structure.pools.DimensionPadding; 29 | import net.minecraft.world.level.levelgen.structure.pools.JigsawPlacement; 30 | import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool; 31 | import net.minecraft.world.level.levelgen.structure.pools.alias.PoolAliasLookup; 32 | import net.minecraft.world.level.levelgen.structure.structures.JigsawStructure; 33 | import net.minecraft.world.level.levelgen.structure.templatesystem.LiquidSettings; 34 | 35 | import java.util.Map; 36 | import java.util.Optional; 37 | 38 | public class OceanStructures extends Structure { 39 | 40 | // A customized structure settings codec to allow us to expand the abilities of the biomes field. 41 | public static final MapCodec CUSTOM_STRUCTURE_SETTINGS_CODEC = RecordCodecBuilder.mapCodec( 42 | codecBuilder -> codecBuilder.group( 43 | // This is where we swapped in our custom codec that will apply the exclude structure tag to remove entries from the has structure tag. 44 | FilterHolderSet.codec(Registries.BIOME, Biome.CODEC, false).fieldOf("biomes").forGetter(x -> x.biomes() instanceof FilterHolderSet filterHolderSet ? filterHolderSet : new FilterHolderSet<>(x.biomes(), HolderSet.empty())), 45 | Codec.simpleMap(MobCategory.CODEC, StructureSpawnOverride.CODEC, StringRepresentable.keys(MobCategory.values())) 46 | .fieldOf("spawn_overrides") 47 | .forGetter(StructureSettings::spawnOverrides), 48 | GenerationStep.Decoration.CODEC.fieldOf("step").forGetter(StructureSettings::step), 49 | TerrainAdjustment.CODEC 50 | .optionalFieldOf("terrain_adaptation", new StructureSettings( 51 | HolderSet.direct(), Map.of(), GenerationStep.Decoration.SURFACE_STRUCTURES, TerrainAdjustment.NONE 52 | ).terrainAdaptation()) 53 | .forGetter(StructureSettings::terrainAdaptation) 54 | ) 55 | .apply(codecBuilder, StructureSettings::new) 56 | ); 57 | 58 | // A custom codec that changes the size limit for our code_structure_sea_boat.json's config to not be capped at 7. 59 | // With this, we can have a structure with a size limit up to 30 if we want to have extremely long branches of pieces in the structure. 60 | public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> 61 | instance.group(CUSTOM_STRUCTURE_SETTINGS_CODEC.forGetter(structureInfo -> structureInfo.modifiableStructureInfo().getOriginalStructureInfo().structureSettings()), 62 | StructureTemplatePool.CODEC.fieldOf("start_pool").forGetter(structure -> structure.startPool), 63 | ResourceLocation.CODEC.optionalFieldOf("start_jigsaw_name").forGetter(structure -> structure.startJigsawName), 64 | Codec.intRange(0, 30).fieldOf("size").forGetter(structure -> structure.size), 65 | HeightProvider.CODEC.fieldOf("start_height").forGetter(structure -> structure.startHeight), 66 | Heightmap.Types.CODEC.optionalFieldOf("project_start_to_heightmap").forGetter(structure -> structure.projectStartToHeightmap), 67 | Codec.intRange(1, 128).fieldOf("max_distance_from_center").forGetter(structure -> structure.maxDistanceFromCenter), 68 | DimensionPadding.CODEC.optionalFieldOf("dimension_padding", JigsawStructure.DEFAULT_DIMENSION_PADDING).forGetter(structure -> structure.dimensionPadding), 69 | LiquidSettings.CODEC.optionalFieldOf("liquid_settings", JigsawStructure.DEFAULT_LIQUID_SETTINGS).forGetter(structure -> structure.liquidSettings) 70 | ).apply(instance, OceanStructures::new)); 71 | 72 | private final Holder startPool; 73 | private final Optional startJigsawName; 74 | private final int size; 75 | private final HeightProvider startHeight; 76 | private final Optional projectStartToHeightmap; 77 | private final int maxDistanceFromCenter; 78 | private final DimensionPadding dimensionPadding; 79 | private final LiquidSettings liquidSettings; 80 | 81 | public OceanStructures(StructureSettings config, 82 | Holder startPool, 83 | Optional startJigsawName, 84 | int size, 85 | HeightProvider startHeight, 86 | Optional projectStartToHeightmap, 87 | int maxDistanceFromCenter, 88 | DimensionPadding dimensionPadding, 89 | LiquidSettings liquidSettings) 90 | { 91 | super(config); 92 | this.startPool = startPool; 93 | this.startJigsawName = startJigsawName; 94 | this.size = size; 95 | this.startHeight = startHeight; 96 | this.projectStartToHeightmap = projectStartToHeightmap; 97 | this.maxDistanceFromCenter = maxDistanceFromCenter; 98 | this.dimensionPadding = dimensionPadding; 99 | this.liquidSettings = liquidSettings; 100 | } 101 | 102 | /* 103 | * This is where extra checks can be done to determine if the structure can spawn here. 104 | * This only needs to be overridden if you're adding additional spawn conditions. 105 | * 106 | * Fun fact, if you set your structure separation/spacing to be 0/1, you can use 107 | * extraSpawningChecks to return true only if certain chunk coordinates are passed in 108 | * which allows you to spawn structures only at certain coordinates in the world. 109 | * 110 | * Basically, this method is used for determining if the land is at a suitable height, 111 | * if certain other structures are too close or not, or some other restrictive condition. 112 | * 113 | * For example, Pillager Outposts added a check to make sure it cannot spawn within 10 chunk of a Village. 114 | * (Bedrock Edition seems to not have the same check) 115 | * 116 | * If you are doing Nether structures, you'll probably want to spawn your structure on top of ledges. 117 | * Best way to do that is to use getBaseColumn to grab a column of blocks at the structure's x/z position. 118 | * Then loop through it and look for land with air above it and set blockpos's Y value to it. 119 | * Make sure to set the final boolean in JigsawPlacement.addPieces to false so 120 | * that the structure spawns at blockpos's y value instead of placing the structure on the Bedrock roof! 121 | * 122 | * Also, please for the love of god, do not do dimension checking here. 123 | * If you do and another mod's dimension is trying to spawn your structure, 124 | * the locate command will make minecraft hang forever and break the game. 125 | * Use the biome tags for where to spawn the structure and users can datapack 126 | * it to spawn in specific biomes that aren't in the dimension they don't like if they wish. 127 | */ 128 | private static boolean extraSpawningChecks(Structure.GenerationContext context) { 129 | // Grabs the chunk position we are at 130 | ChunkPos chunkpos = context.chunkPos(); 131 | 132 | // Get first non-air block. 133 | int occupiedYPos = context.chunkGenerator().getFirstOccupiedHeight( 134 | chunkpos.getMinBlockX(), 135 | chunkpos.getMinBlockZ(), 136 | Heightmap.Types.WORLD_SURFACE_WG, 137 | context.heightAccessor(), 138 | context.randomState()); 139 | 140 | // Get column of blocks at corner of the chunk. BEWARE, getBaseColumn is an expensive call. Call this as few times as possible for your checks. 141 | // Note, this column of blocks only has the raw terrain of the world which for the Overworld is Stone, Water, and Air. 142 | NoiseColumn columnOfBlocks = context.chunkGenerator().getBaseColumn(chunkpos.getBlockX(0), chunkpos.getBlockZ(0), context.heightAccessor(), context.randomState()); 143 | 144 | // Grab the block at the specified Y value. 145 | BlockState blockState = columnOfBlocks.getBlock(occupiedYPos); 146 | 147 | // Checks to make sure our structure only spawns if the spot has water. 148 | return blockState.getFluidState().is(FluidTags.WATER); 149 | } 150 | 151 | @Override 152 | public Optional findGenerationPoint(GenerationContext context) { 153 | 154 | // Check if the spot is valid for our structure. This is just as another method for cleanness. 155 | // Returning an empty optional tells the game to skip this spot as it will not generate the structure. 156 | if (!OceanStructures.extraSpawningChecks(context)) { 157 | return Optional.empty(); 158 | } 159 | 160 | // Set's our spawning blockpos's y offset 161 | int startY = this.startHeight.sample(context.random(), new WorldGenerationContext(context.chunkGenerator(), context.heightAccessor())); 162 | 163 | // Turns the chunk coordinates into actual coordinates we can use. (Gets corner of that chunk) 164 | ChunkPos chunkPos = context.chunkPos(); 165 | BlockPos blockPos = new BlockPos(chunkPos.getMinBlockX(), startY, chunkPos.getMinBlockZ()); 166 | 167 | Optional structurePiecesGenerator = 168 | JigsawPlacement.addPieces( 169 | context, // Used for JigsawPlacement to get all the proper behaviors done. 170 | this.startPool, // The starting pool to use to create the structure layout from 171 | this.startJigsawName, // Can be used to only spawn from one Jigsaw block. But we don't need to worry about this. 172 | this.size, // How deep a branch of pieces can go away from center piece. (5 means branches cannot be longer than 5 pieces from center piece) 173 | blockPos, // Where to spawn the structure. 174 | false, // "useExpansionHack" This is for legacy villages to generate properly. You should keep this false always. 175 | this.projectStartToHeightmap, // Adds the terrain height's y value to the passed in blockpos's y value. (This uses WORLD_SURFACE_WG heightmap which stops at top water too) 176 | // Here at projectStartToHeightmap, start_height's y value is -1 which means the structure spawn -1 blocks below terrain height if start_height and project_start_to_heightmap is defined in structure JSON. 177 | // Set projectStartToHeightmap to be empty optional for structure to be place only at the passed in blockpos's Y value instead. 178 | // Definitely keep this an empty optional when placing structures in the nether as otherwise, heightmap placing will put the structure on the Bedrock roof. 179 | this.maxDistanceFromCenter, // Maximum limit for how far pieces can spawn from center. You cannot set this bigger than 128 or else pieces gets cutoff. 180 | PoolAliasLookup.EMPTY, // Optional thing that allows swapping a template pool with another per structure json instance. We don't need this but see vanilla JigsawStructure class for how to wire it up if you want it. 181 | this.dimensionPadding, // Optional thing to prevent generating too close to the bottom or top of the dimension. 182 | this.liquidSettings); // Optional thing to control whether the structure will be waterlogged when replacing pre-existing water in the world. 183 | 184 | /* 185 | * Note, you are always free to make your own JigsawPlacement class and implementation of how the structure 186 | * should generate. It is tricky but extremely powerful if you are doing something that vanilla's jigsaw system cannot do. 187 | * Such as for example, forcing 3 pieces to always spawn every time, limiting how often a piece spawns, or remove the intersection limitation of pieces. 188 | */ 189 | 190 | // Return the pieces generator that is now set up so that the game runs it when it needs to create the layout of structure pieces. 191 | return structurePiecesGenerator; 192 | } 193 | 194 | @Override 195 | public StructureType type() { 196 | return STStructures.OCEAN_STRUCTURES.get(); // Helps the game know how to turn this structure back to json to save to chunks 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/main/java/com/telepathicgrunt/structure_tutorial/structures/SkyStructures.java: -------------------------------------------------------------------------------- 1 | package com.telepathicgrunt.structure_tutorial.structures; 2 | 3 | import com.mojang.serialization.Codec; 4 | import com.mojang.serialization.MapCodec; 5 | import com.mojang.serialization.codecs.RecordCodecBuilder; 6 | import com.telepathicgrunt.structure_tutorial.STStructures; 7 | import net.minecraft.core.BlockPos; 8 | import net.minecraft.core.Holder; 9 | import net.minecraft.resources.ResourceLocation; 10 | import net.minecraft.world.level.ChunkPos; 11 | import net.minecraft.world.level.levelgen.Heightmap; 12 | import net.minecraft.world.level.levelgen.WorldGenerationContext; 13 | import net.minecraft.world.level.levelgen.heightproviders.HeightProvider; 14 | import net.minecraft.world.level.levelgen.structure.Structure; 15 | import net.minecraft.world.level.levelgen.structure.StructureType; 16 | import net.minecraft.world.level.levelgen.structure.pools.DimensionPadding; 17 | import net.minecraft.world.level.levelgen.structure.pools.JigsawPlacement; 18 | import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool; 19 | import net.minecraft.world.level.levelgen.structure.pools.alias.PoolAliasLookup; 20 | import net.minecraft.world.level.levelgen.structure.structures.JigsawStructure; 21 | import net.minecraft.world.level.levelgen.structure.templatesystem.LiquidSettings; 22 | 23 | import java.util.Optional; 24 | 25 | public class SkyStructures extends Structure { 26 | 27 | // A custom codec that changes the size limit for our code_structure_sky_fan.json's config to not be capped at 7. 28 | // With this, we can have a structure with a size limit up to 30 if we want to have extremely long branches of pieces in the structure. 29 | public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> 30 | instance.group(SkyStructures.settingsCodec(instance), 31 | StructureTemplatePool.CODEC.fieldOf("start_pool").forGetter(structure -> structure.startPool), 32 | ResourceLocation.CODEC.optionalFieldOf("start_jigsaw_name").forGetter(structure -> structure.startJigsawName), 33 | Codec.intRange(0, 30).fieldOf("size").forGetter(structure -> structure.size), 34 | HeightProvider.CODEC.fieldOf("start_height").forGetter(structure -> structure.startHeight), 35 | Heightmap.Types.CODEC.optionalFieldOf("project_start_to_heightmap").forGetter(structure -> structure.projectStartToHeightmap), 36 | Codec.intRange(1, 128).fieldOf("max_distance_from_center").forGetter(structure -> structure.maxDistanceFromCenter), 37 | DimensionPadding.CODEC.optionalFieldOf("dimension_padding", JigsawStructure.DEFAULT_DIMENSION_PADDING).forGetter(structure -> structure.dimensionPadding), 38 | LiquidSettings.CODEC.optionalFieldOf("liquid_settings", JigsawStructure.DEFAULT_LIQUID_SETTINGS).forGetter(structure -> structure.liquidSettings) 39 | ).apply(instance, SkyStructures::new)); 40 | 41 | private final Holder startPool; 42 | private final Optional startJigsawName; 43 | private final int size; 44 | private final HeightProvider startHeight; 45 | private final Optional projectStartToHeightmap; 46 | private final int maxDistanceFromCenter; 47 | private final DimensionPadding dimensionPadding; 48 | private final LiquidSettings liquidSettings; 49 | 50 | public SkyStructures(Structure.StructureSettings config, 51 | Holder startPool, 52 | Optional startJigsawName, 53 | int size, 54 | HeightProvider startHeight, 55 | Optional projectStartToHeightmap, 56 | int maxDistanceFromCenter, 57 | DimensionPadding dimensionPadding, 58 | LiquidSettings liquidSettings) 59 | { 60 | super(config); 61 | this.startPool = startPool; 62 | this.startJigsawName = startJigsawName; 63 | this.size = size; 64 | this.startHeight = startHeight; 65 | this.projectStartToHeightmap = projectStartToHeightmap; 66 | this.maxDistanceFromCenter = maxDistanceFromCenter; 67 | this.dimensionPadding = dimensionPadding; 68 | this.liquidSettings = liquidSettings; 69 | } 70 | 71 | /* 72 | * This is where extra checks can be done to determine if the structure can spawn here. 73 | * This only needs to be overridden if you're adding additional spawn conditions. 74 | * 75 | * Fun fact, if you set your structure separation/spacing to be 0/1, you can use 76 | * extraSpawningChecks to return true only if certain chunk coordinates are passed in 77 | * which allows you to spawn structures only at certain coordinates in the world. 78 | * 79 | * Basically, this method is used for determining if the land is at a suitable height, 80 | * if certain other structures are too close or not, or some other restrictive condition. 81 | * 82 | * For example, Pillager Outposts added a check to make sure it cannot spawn within 10 chunk of a Village. 83 | * (Bedrock Edition seems to not have the same check) 84 | * 85 | * If you are doing Nether structures, you'll probably want to spawn your structure on top of ledges. 86 | * Best way to do that is to use getBaseColumn to grab a column of blocks at the structure's x/z position. 87 | * Then loop through it and look for land with air above it and set blockpos's Y value to it. 88 | * Make sure to set the final boolean in JigsawPlacement.addPieces to false so 89 | * that the structure spawns at blockpos's y value instead of placing the structure on the Bedrock roof! 90 | * 91 | * Also, please for the love of god, do not do dimension checking here. 92 | * If you do and another mod's dimension is trying to spawn your structure, 93 | * the locate command will make minecraft hang forever and break the game. 94 | * Use the biome tags for where to spawn the structure and users can datapack 95 | * it to spawn in specific biomes that aren't in the dimension they don't like if they wish. 96 | */ 97 | private static boolean extraSpawningChecks(Structure.GenerationContext context) { 98 | // Grabs the chunk position we are at 99 | ChunkPos chunkpos = context.chunkPos(); 100 | 101 | // Checks to make sure our structure does not spawn above land that's higher than y = 150 102 | // to demonstrate how this method is good for checking extra conditions for spawning 103 | return context.chunkGenerator().getFirstOccupiedHeight( 104 | chunkpos.getMinBlockX(), 105 | chunkpos.getMinBlockZ(), 106 | Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, 107 | context.heightAccessor(), 108 | context.randomState()) < 150; 109 | } 110 | 111 | @Override 112 | public Optional findGenerationPoint(Structure.GenerationContext context) { 113 | 114 | // Check if the spot is valid for our structure. This is just as another method for cleanness. 115 | // Returning an empty optional tells the game to skip this spot as it will not generate the structure. 116 | if (!SkyStructures.extraSpawningChecks(context)) { 117 | return Optional.empty(); 118 | } 119 | 120 | // Set's our spawning blockpos's y offset. 121 | int startY = this.startHeight.sample(context.random(), new WorldGenerationContext(context.chunkGenerator(), context.heightAccessor())); 122 | 123 | // Turns the chunk coordinates into actual coordinates we can use. (Gets corner of that chunk) 124 | ChunkPos chunkPos = context.chunkPos(); 125 | BlockPos blockPos = new BlockPos(chunkPos.getMinBlockX(), startY, chunkPos.getMinBlockZ()); 126 | 127 | Optional structurePiecesGenerator = 128 | JigsawPlacement.addPieces( 129 | context, // Used for JigsawPlacement to get all the proper behaviors done. 130 | this.startPool, // The starting pool to use to create the structure layout from 131 | this.startJigsawName, // Can be used to only spawn from one Jigsaw block. But we don't need to worry about this. 132 | this.size, // How deep a branch of pieces can go away from center piece. (5 means branches cannot be longer than 5 pieces from center piece) 133 | blockPos, // Where to spawn the structure. 134 | false, // "useExpansionHack" This is for legacy villages to generate properly. You should keep this false always. 135 | this.projectStartToHeightmap, // Adds the terrain height's y value to the passed in blockpos's y value. (This uses WORLD_SURFACE_WG heightmap which stops at top water too) 136 | // Here at projectStartToHeightmap, start_height's y value is 60 which means the structure spawn 60 blocks above terrain height if start_height and project_start_to_heightmap is defined in structure JSON. 137 | // Set projectStartToHeightmap to be empty optional for structure to be place only at the passed in blockpos's Y value instead. 138 | // Definitely keep this an empty optional when placing structures in the nether as otherwise, heightmap placing will put the structure on the Bedrock roof. 139 | this.maxDistanceFromCenter, // Maximum limit for how far pieces can spawn from center. You cannot set this bigger than 128 or else pieces gets cutoff. 140 | PoolAliasLookup.EMPTY, // Optional thing that allows swapping a template pool with another per structure json instance. We don't need this but see vanilla JigsawStructure class for how to wire it up if you want it. 141 | this.dimensionPadding, // Optional thing to prevent generating too close to the bottom or top of the dimension. 142 | this.liquidSettings); // Optional thing to control whether the structure will be waterlogged when replacing pre-existing water in the world. 143 | 144 | /* 145 | * Note, you are always free to make your own JigsawPlacement class and implementation of how the structure 146 | * should generate. It is tricky but extremely powerful if you are doing something that vanilla's jigsaw system cannot do. 147 | * Such as for example, forcing 3 pieces to always spawn every time, limiting how often a piece spawns, or remove the intersection limitation of pieces. 148 | */ 149 | 150 | // Return the pieces generator that is now set up so that the game runs it when it needs to create the layout of structure pieces. 151 | return structurePiecesGenerator; 152 | } 153 | 154 | @Override 155 | public StructureType type() { 156 | return STStructures.SKY_STRUCTURES.get(); // Helps the game know how to turn this structure back to json to save to chunks 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/main/java/com/telepathicgrunt/structure_tutorial/utilities/FilterHolderSet.java: -------------------------------------------------------------------------------- 1 | package com.telepathicgrunt.structure_tutorial.utilities; 2 | 3 | import com.mojang.datafixers.util.Either; 4 | import com.mojang.serialization.Codec; 5 | import com.mojang.serialization.MapCodec; 6 | import com.mojang.serialization.codecs.RecordCodecBuilder; 7 | import net.minecraft.core.Holder; 8 | import net.minecraft.core.HolderOwner; 9 | import net.minecraft.core.HolderSet; 10 | import net.minecraft.core.Registry; 11 | import net.minecraft.resources.HolderSetCodec; 12 | import net.minecraft.resources.ResourceKey; 13 | import net.minecraft.tags.TagKey; 14 | import net.minecraft.util.RandomSource; 15 | 16 | import java.util.Iterator; 17 | import java.util.List; 18 | import java.util.Optional; 19 | import java.util.Set; 20 | import java.util.stream.Collectors; 21 | import java.util.stream.Stream; 22 | 23 | /** 24 | * A special HolderSet that holds one HolderSet as the base entries and uses a second HolderSet as a filter on those entries. 25 | * You can copy and paste this to your project. You do not need to make any changes here. 26 | * Simply use this in your structure's codec to replace the biomes HolderSet field. See OceanStructures.java for an example of usage. 27 | */ 28 | public class FilterHolderSet implements HolderSet { 29 | public static MapCodec> codec(ResourceKey> registryKey, Codec> holderCodec, boolean forceList) { 30 | return RecordCodecBuilder.mapCodec( 31 | builder -> builder 32 | .group( 33 | HolderSetCodec.create(registryKey, holderCodec, forceList).fieldOf("base").forGetter(FilterHolderSet::base), 34 | HolderSetCodec.create(registryKey, holderCodec, forceList).fieldOf("filter").forGetter(FilterHolderSet::filter)) 35 | .apply(builder, FilterHolderSet::new)); 36 | } 37 | 38 | private final HolderSet base; 39 | private final HolderSet filter; 40 | 41 | private Set> set = null; 42 | private List> list = null; 43 | 44 | public HolderSet base() { 45 | return this.base; 46 | } 47 | public HolderSet filter() { 48 | return this.filter; 49 | } 50 | 51 | public FilterHolderSet(HolderSet base, HolderSet filter) { 52 | this.base = base; 53 | this.filter = filter; 54 | } 55 | 56 | /** 57 | * {@return immutable Set of filtered Holders} 58 | */ 59 | protected Set> createSet() { 60 | return this.base 61 | .stream() 62 | .filter(holder -> !this.filter.contains(holder)) 63 | .collect(Collectors.toSet()); 64 | } 65 | 66 | public Set> getSet() { 67 | Set> thisSet = this.set; 68 | if (thisSet == null) { 69 | Set> set = this.createSet(); 70 | this.set = set; 71 | return set; 72 | } else { 73 | return thisSet; 74 | } 75 | } 76 | 77 | public List> getList() { 78 | List> thisList = this.list; 79 | if (thisList == null) { 80 | List> list = List.copyOf(this.getSet()); 81 | this.list = list; 82 | return list; 83 | } else { 84 | return thisList; 85 | } 86 | } 87 | 88 | @Override 89 | public Stream> stream() { 90 | return this.getList().stream(); 91 | } 92 | 93 | @Override 94 | public int size() { 95 | return this.getList().size(); 96 | } 97 | 98 | @Override 99 | public Either, List>> unwrap() { 100 | return Either.right(this.getList()); 101 | } 102 | 103 | @Override 104 | public Optional> getRandomElement(RandomSource rand) { 105 | List> list = this.getList(); 106 | int size = list.size(); 107 | return size > 0 108 | ? Optional.of(list.get(rand.nextInt(size))) 109 | : Optional.empty(); 110 | } 111 | 112 | @Override 113 | public Holder get(int i) { 114 | return this.getList().get(i); 115 | } 116 | 117 | @Override 118 | public boolean contains(Holder holder) { 119 | return this.getSet().contains(holder); 120 | } 121 | 122 | @Override 123 | public boolean canSerializeIn(HolderOwner holderOwner) { 124 | return this.base.canSerializeIn(holderOwner) && this.filter.canSerializeIn(holderOwner); 125 | } 126 | 127 | @Override 128 | public Optional> unwrapKey() { 129 | return Optional.empty(); 130 | } 131 | 132 | @Override 133 | public Iterator> iterator() { 134 | return this.getList().iterator(); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/neoforge.mods.toml: -------------------------------------------------------------------------------- 1 | modLoader="javafml" 2 | loaderVersion="${loader_version_range}" 3 | license="${mod_license}" 4 | 5 | [[mods]] 6 | modId="${mod_id}" 7 | version="${mod_version}" 8 | displayName="${mod_name}" 9 | authors="${mod_authors}" 10 | credits="${mod_credits}" 11 | description='''${mod_description}''' 12 | 13 | # makes sure our mod does not be attempted to be ran on older version of Minecraft and explodes 14 | [[dependencies.${mod_id}]] 15 | modId="neoforge" 16 | type="required" 17 | versionRange="${neo_version_range}" 18 | ordering="NONE" 19 | side="BOTH" 20 | 21 | [[dependencies.${mod_id}]] 22 | modId="minecraft" 23 | type="required" 24 | versionRange="${minecraft_version_range}" 25 | ordering="NONE" 26 | side="BOTH" 27 | -------------------------------------------------------------------------------- /src/main/resources/data/minecraft/tags/worldgen/structure/on_ocean_explorer_maps.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | 4 | // Minecraft has some structure tags that can add extra behaviors 5 | // This one add your structure as a possible locatable structure to Cartographer Villager's Ocean Monument maps 6 | "values": [ 7 | "structure_tutorial:code_structure_sky_fan" 8 | ] 9 | } -------------------------------------------------------------------------------- /src/main/resources/data/structure_tutorial/structure/end_phantom_balloon.nbt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TelepathicGrunt/StructureTutorialMod/10334b0c90f0dce3d0b7aecf6087cd6b7ea1d0c9/src/main/resources/data/structure_tutorial/structure/end_phantom_balloon.nbt -------------------------------------------------------------------------------- /src/main/resources/data/structure_tutorial/structure/run_down_house_left_side.nbt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TelepathicGrunt/StructureTutorialMod/10334b0c90f0dce3d0b7aecf6087cd6b7ea1d0c9/src/main/resources/data/structure_tutorial/structure/run_down_house_left_side.nbt -------------------------------------------------------------------------------- /src/main/resources/data/structure_tutorial/structure/run_down_house_right_side.nbt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TelepathicGrunt/StructureTutorialMod/10334b0c90f0dce3d0b7aecf6087cd6b7ea1d0c9/src/main/resources/data/structure_tutorial/structure/run_down_house_right_side.nbt -------------------------------------------------------------------------------- /src/main/resources/data/structure_tutorial/structure/run_down_house_right_side_golden.nbt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TelepathicGrunt/StructureTutorialMod/10334b0c90f0dce3d0b7aecf6087cd6b7ea1d0c9/src/main/resources/data/structure_tutorial/structure/run_down_house_right_side_golden.nbt -------------------------------------------------------------------------------- /src/main/resources/data/structure_tutorial/structure/sea_boat.nbt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TelepathicGrunt/StructureTutorialMod/10334b0c90f0dce3d0b7aecf6087cd6b7ea1d0c9/src/main/resources/data/structure_tutorial/structure/sea_boat.nbt -------------------------------------------------------------------------------- /src/main/resources/data/structure_tutorial/structure/sky_fan.nbt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TelepathicGrunt/StructureTutorialMod/10334b0c90f0dce3d0b7aecf6087cd6b7ea1d0c9/src/main/resources/data/structure_tutorial/structure/sky_fan.nbt -------------------------------------------------------------------------------- /src/main/resources/data/structure_tutorial/tags/worldgen/biome/exclude_structure/code_structure_sea_boat_biomes.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | 4 | "_comment1": " This biome tag can specify the biome directly. Or specify another biome tag by starting with # ", 5 | "_comment2": " An entry can also be expanded to have a 'required' field set to false. This entry will be skipped if the tag or biome doesn't exist. ", 6 | "_comment3": " This is perfect for supporting modded and datapack biomes! NeoForge provides `c` tags that you can use. ", 7 | "_comment4": " NeoForge `c` tags: https://github.com/neoforged/NeoForge/tree/1.21.1/src/generated/resources/data/c/tags/worldgen/biome ", 8 | "_comment5": " Minecraft `minecraft` tags: https://github.com/misode/mcmeta/tree/b7e7f5282f9cc2ca1bd01e9a4911beab4725cb4b/data/minecraft/tags/worldgen/biome ", 9 | "_comment6": " Please use `c` and `minecraft` biome tags as much as possible to maximize mod/datapack compatibility. ", 10 | "values": [ 11 | "minecraft:frozen_ocean", 12 | "minecraft:deep_frozen_ocean" 13 | ] 14 | } -------------------------------------------------------------------------------- /src/main/resources/data/structure_tutorial/tags/worldgen/biome/has_structure/code_structure_end_phantom_balloon_biomes.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | 4 | "_comment1": " This biome tag can specify the biome directly. Or specify another biome tag by starting with # ", 5 | "_comment2": " An entry can also be expanded to have a 'required' field set to false. This entry will be skipped if the tag or biome doesn't exist. ", 6 | "_comment3": " This is perfect for supporting modded and datapack biomes! NeoForge provides `c` tags that you can use. ", 7 | "_comment4": " NeoForge `c` tags: https://github.com/neoforged/NeoForge/tree/1.21.1/src/generated/resources/data/c/tags/worldgen/biome ", 8 | "_comment5": " Minecraft `minecraft` tags: https://github.com/misode/mcmeta/tree/b7e7f5282f9cc2ca1bd01e9a4911beab4725cb4b/data/minecraft/tags/worldgen/biome ", 9 | "_comment6": " Please use `c` and `minecraft` biome tags as much as possible to maximize mod/datapack compatibility. ", 10 | "_comment7": " For this tag, you could add `c:is_outer_end_island` instead of the two below tags if you only want biomes that are big islands. ", 11 | "values": [ 12 | "#minecraft:is_end", 13 | { 14 | "id": "#c:is_end", 15 | "required": false 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /src/main/resources/data/structure_tutorial/tags/worldgen/biome/has_structure/code_structure_sea_boat_biomes.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | 4 | "_comment1": " This biome tag can specify the biome directly. Or specify another biome tag by starting with # ", 5 | "_comment2": " An entry can also be expanded to have a 'required' field set to false. This entry will be skipped if the tag or biome doesn't exist. ", 6 | "_comment3": " This is perfect for supporting modded and datapack biomes! NeoForge provides `c` tags that you can use. ", 7 | "_comment4": " NeoForge `c` tags: https://github.com/neoforged/NeoForge/tree/1.21.1/src/generated/resources/data/c/tags/worldgen/biome ", 8 | "_comment5": " Minecraft `minecraft` tags: https://github.com/misode/mcmeta/tree/b7e7f5282f9cc2ca1bd01e9a4911beab4725cb4b/data/minecraft/tags/worldgen/biome ", 9 | "_comment6": " Please use `c` and `minecraft` biome tags as much as possible to maximize mod/datapack compatibility. ", 10 | "values": [ 11 | "#minecraft:is_ocean", 12 | "#minecraft:is_deep_ocean", 13 | { 14 | "id": "#c:is_ocean", 15 | "required": false 16 | }, 17 | { 18 | "id": "#c:is_deep_ocean", 19 | "required": false 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /src/main/resources/data/structure_tutorial/tags/worldgen/biome/has_structure/code_structure_sky_fan_biomes.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | 4 | "_comment1": " This biome tag can specify the biome directly. Or specify another biome tag by starting with # ", 5 | "_comment2": " An entry can also be expanded to have a 'required' field set to false. This entry will be skipped if the tag or biome doesn't exist. ", 6 | "_comment3": " This is perfect for supporting modded and datapack biomes! NeoForge provides `c` tags that you can use. ", 7 | "_comment4": " NeoForge `c` tags: https://github.com/neoforged/NeoForge/tree/1.21.1/src/generated/resources/data/c/tags/worldgen/biome ", 8 | "_comment5": " Minecraft `minecraft` tags: https://github.com/misode/mcmeta/tree/b7e7f5282f9cc2ca1bd01e9a4911beab4725cb4b/data/minecraft/tags/worldgen/biome ", 9 | "_comment6": " Please use `c` and `minecraft` biome tags as much as possible to maximize mod/datapack compatibility. ", 10 | "values": [ 11 | "#minecraft:is_mountain", 12 | "#minecraft:is_ocean", 13 | "#minecraft:is_deep_ocean", 14 | "minecraft:swamp", 15 | { 16 | "id": "#c:is_mountain", 17 | "required": false 18 | }, 19 | { 20 | "id": "#c:is_ocean", 21 | "required": false 22 | }, 23 | { 24 | "id": "#c:is_deep_ocean", 25 | "required": false 26 | }, 27 | { 28 | "id": "#c:is_swamp", 29 | "required": false 30 | } 31 | ] 32 | } -------------------------------------------------------------------------------- /src/main/resources/data/structure_tutorial/tags/worldgen/biome/has_structure/json_only_house_biomes.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | 4 | "_comment1": " This biome tag can specify the biome directly. Or specify another biome tag by starting with # ", 5 | "_comment2": " An entry can also be expanded to have a 'required' field set to false. This entry will be skipped if the tag or biome doesn't exist. ", 6 | "_comment3": " This is perfect for supporting modded and datapack biomes! NeoForge provides `c` tags that you can use. ", 7 | "_comment4": " NeoForge `c` tags: https://github.com/neoforged/NeoForge/tree/1.21.1/src/generated/resources/data/c/tags/worldgen/biome ", 8 | "_comment5": " Minecraft `minecraft` tags: https://github.com/misode/mcmeta/tree/b7e7f5282f9cc2ca1bd01e9a4911beab4725cb4b/data/minecraft/tags/worldgen/biome ", 9 | "_comment6": " Please use `c` and `minecraft` biome tags as much as possible to maximize mod/datapack compatibility. ", 10 | "values": [ 11 | "#minecraft:is_jungle", 12 | "#minecraft:is_forest", 13 | "#minecraft:is_taiga", 14 | "minecraft:desert", 15 | "minecraft:plains", 16 | "minecraft:snowy_plains", 17 | "minecraft:sunflower_plains", 18 | "minecraft:savanna", 19 | "minecraft:savanna_plateau", 20 | "minecraft:windswept_savanna", 21 | { 22 | "id": "#c:is_jungle", 23 | "required": false 24 | }, 25 | { 26 | "id": "#c:is_forest", 27 | "required": false 28 | }, 29 | { 30 | "id": "#c:is_taiga", 31 | "required": false 32 | }, 33 | { 34 | "id": "#c:is_desert", 35 | "required": false 36 | }, 37 | { 38 | "id": "#c:is_plains", 39 | "required": false 40 | }, 41 | { 42 | "id": "#c:is_snowy_plains", 43 | "required": false 44 | }, 45 | { 46 | "id": "#c:is_savanna", 47 | "required": false 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /src/main/resources/data/structure_tutorial/worldgen/processor_list/randomize_stonebrick.json: -------------------------------------------------------------------------------- 1 | { 2 | // Processor lists will run a processor for every block placed by the nbt file. 3 | // The resultant block of the processor list is the block that actually gets placed in the world. 4 | "processors": [ 5 | // A single Rule processor for this list. 6 | { 7 | "processor_type": "minecraft:rule", 8 | 9 | // The rules for this Rule processor to use 10 | "rules": [ 11 | { 12 | // The block to look for from the nbt file 13 | // This has a 50% chance of allowing the found stone bricks block to be replaced 14 | "input_predicate": { 15 | "block": "minecraft:stone_bricks", 16 | "probability": 0.5, 17 | "predicate_type": "minecraft:random_block_match" 18 | }, 19 | // This is always true 20 | // Village roads have this part look for water so roads can be replaced with oak planks instead 21 | "location_predicate": { 22 | "predicate_type": "minecraft:always_true" 23 | }, 24 | // The actual block to place if the above conditions return true 25 | "output_state": { 26 | "Name": "minecraft:infested_stone_bricks" 27 | } 28 | }, 29 | { 30 | "input_predicate": { 31 | "block": "minecraft:stone_bricks", 32 | "probability": 0.05, 33 | "predicate_type": "minecraft:random_block_match" 34 | }, 35 | "location_predicate": { 36 | "predicate_type": "minecraft:always_true" 37 | }, 38 | "output_state": { 39 | "Name": "minecraft:emerald_ore" 40 | } 41 | }, 42 | { 43 | "input_predicate": { 44 | "block": "minecraft:cobbled_deepslate", 45 | "probability": 0.1, 46 | "predicate_type": "minecraft:random_block_match" 47 | }, 48 | "location_predicate": { 49 | "predicate_type": "minecraft:always_true" 50 | }, 51 | // If an output_state block has properties, you must define ALL properties of the block! 52 | "output_state": { 53 | "Name": "minecraft:deepslate_redstone_ore", 54 | "Properties": { 55 | "lit": false 56 | } 57 | } 58 | } 59 | ] 60 | } 61 | ] 62 | } -------------------------------------------------------------------------------- /src/main/resources/data/structure_tutorial/worldgen/structure/code_structure_end_phantom_balloon.json: -------------------------------------------------------------------------------- 1 | { 2 | // The base structure class to use for the behavior of the structure. (Like extra terrain checks and such) 3 | // Since we are using a custom structure class with its own codec, you'll notice there's no use_expansion_hack field 4 | // below like the minecraft:jigsaw structure type has. This is because our custom structure's codec does not have 5 | // that field because we should not need expansion hacks for our structures. It's only for legacy Minecraft villages. 6 | "type": "structure_tutorial:end_island_structures", 7 | 8 | // the path to the template pool json file to use 9 | "start_pool": "structure_tutorial:end_phantom_balloon", 10 | 11 | // If this field is present, the game will look for a downward facing Jigsaw Block with this Name field in the chosen start piece. 12 | // Once found, it will then spawn the start piece so that it's Jigsaw Block is now the true structure center position. 13 | // If this field is not present, the start piece will spawn where the structure's true scenter position is the corner of the starting piece. 14 | "start_jigsaw_name": "center", 15 | 16 | // This is how many pieces away from the starting piece a piece of the structure can spawn 17 | // Think of it like the length of the branch of the structure 18 | "size": 1, 19 | 20 | // Maximum distance away from center that pieces can spawn. Cannot be greater than 128 21 | "max_distance_from_center": 80, 22 | 23 | // The biome tag to use for what biomes that this structure can spawn in 24 | "biomes": "#structure_tutorial:has_structure/code_structure_end_phantom_balloon_biomes", 25 | 26 | // The generation step for when to generate the structure. there are 10 stages you can pick from! 27 | // This surface structure stage places the structure before plants and ores are generated 28 | // See GenerationStep.Feature enum for all the stages you can use and what order they are in 29 | "step": "surface_structures", 30 | 31 | // Where to spawn our structure at what y value if project_start_to_heightmap is not present. 32 | // start_height can be used to spawn at a random fixed y value by doing something like: "max_inclusive": { "below_top": 10 }, "min_inclusive": { "above_bottom": 32 } 33 | // If project_start_to_heightmap is present, the y value chosen in start_height will be added to the terrain's y value. 34 | // So a start height that gives -5 will sink the structure 5 blocks into the terrain. Here, we will spawn this structure 60 blocks above the terrain. 35 | "start_height": { 36 | "absolute": 20 37 | }, 38 | 39 | // Makes our sky fan structure take the terrain's top y value and add it to the start_height y value above. 40 | // The final value is the y value the structures spawn at. 41 | // WORLD_SURFACE_WG will stop at first non-air block so it spawns above oceans always instead of sunken into a deep sea. 42 | "project_start_to_heightmap": "WORLD_SURFACE_WG", 43 | 44 | // What mobs can spawn over time in the structure. 45 | // Make sure you add the mob to the right category (monster, creature, etc) 46 | "spawn_overrides": {} 47 | } -------------------------------------------------------------------------------- /src/main/resources/data/structure_tutorial/worldgen/structure/code_structure_sea_boat.json: -------------------------------------------------------------------------------- 1 | { 2 | // The base structure class to use for the behavior of the structure. (Like extra terrain checks and such) 3 | // Since we are using a custom structure class with its own codec, you'll notice there's no use_expansion_hack field 4 | // below like the minecraft:jigsaw structure type has. This is because our custom structure's codec does not have 5 | // that field because we should not need expansion hacks for our structures. It's only for legacy Minecraft villages. 6 | "type": "structure_tutorial:ocean_structures", 7 | 8 | // The path to the template pool json file to use 9 | "start_pool": "structure_tutorial:sea_boat", 10 | 11 | // If this field is present, the game will look for a downward facing Jigsaw Block with this Name field in the chosen start piece. 12 | // Once found, it will then spawn the start piece so that it's Jigsaw Block is now the true structure center position. 13 | // If this field is not present, the start piece will spawn where the structure's true scenter position is the corner of the starting piece. 14 | "start_jigsaw_name": "center", 15 | 16 | // This is how many pieces away from the starting piece a piece of the structure can spawn 17 | // Think of it like the length of the branch of the structureS 18 | "size": 1, 19 | 20 | // Maximum distance away from center that pieces can spawn. Cannot be greater than 128 21 | "max_distance_from_center": 80, 22 | 23 | // Special custom implementation of the biomes field to allow reading two tags at once to allow excluding biomes by tag too! 24 | // See the Codecs in OceanStructures.java for how this field was overridden and changed. 25 | "biomes": { 26 | // The biome tag to use for what biomes that this structure can spawn in. 27 | // Here, this contains all ocean biomes tags so all oceans are included 28 | "base": "#structure_tutorial:has_structure/code_structure_sea_boat_biomes", 29 | // The biome tag to use for what biomes that this structure should NOT spawn in, even if the biome is in the has_structure entry. 30 | // Here we exclude Frozen Ocean and Deep Frozen Ocean because of icebergs. Not good for our ship. 31 | "filter": "#structure_tutorial:exclude_structure/code_structure_sea_boat_biomes" 32 | }, 33 | 34 | // The generation step for when to generate the structure. there are 10 stages you can pick from! 35 | // This surface structure stage places the structure before plants and ores are generated 36 | // See GenerationStep.Feature enum for all the stages you can use and what order they are in 37 | "step": "surface_structures", 38 | 39 | // Where to spawn our structure at what y value if project_start_to_heightmap is not present. 40 | // start_height can be used to spawn at a random fixed y value by doing something like: "max_inclusive": { "below_top": 10 }, "min_inclusive": { "above_bottom": 32 } 41 | // If project_start_to_heightmap is present, the y value chosen in start_height will be added to the terrain's y value. 42 | // So a start height that gives -5 will sink the structure 5 blocks into the terrain. Here, we will spawn this structure 1 blocks below the terrain. 43 | "start_height": { 44 | "absolute": -1 45 | }, 46 | 47 | // Makes our sky fan structure take the terrain's top y value and add it to the start_height y value above. 48 | // The final value is the y value the structures spawn at. 49 | // WORLD_SURFACE_WG will stop at first non-air block so it spawns above oceans always instead of sunken into a deep sea. 50 | "project_start_to_heightmap": "WORLD_SURFACE_WG", 51 | 52 | // Optional thing to control whether the structure will be waterlogged when replacing pre-existing water in the world. 53 | // Set this to "ignore_waterlogging" if you are making underground structures that you do not want to get waterlogged and flooded by aquifiers. 54 | "liquid_settings": "apply_waterlogging", 55 | 56 | // What mobs can spawn over time in the structure. 57 | // Make sure you add the mob to the right category (monster, creature, etc) 58 | "spawn_overrides": {} 59 | } -------------------------------------------------------------------------------- /src/main/resources/data/structure_tutorial/worldgen/structure/code_structure_sky_fan.json: -------------------------------------------------------------------------------- 1 | { 2 | // The base structure class to use for the behavior of the structure. (Like extra terrain checks and such) 3 | // Since we are using a custom structure class with its own codec, you'll notice there's no use_expansion_hack field 4 | // below like the minecraft:jigsaw structure type has. This is because our custom structure's codec does not have 5 | // that field because we should not need expansion hacks for our structures. It's only for legacy Minecraft villages. 6 | "type": "structure_tutorial:sky_structures", 7 | 8 | // the path to the template pool json file to use 9 | "start_pool": "structure_tutorial:sky_fan", 10 | 11 | // This is how many pieces away from the starting piece a piece of the structure can spawn 12 | // Think of it like the length of the branch of the structure 13 | "size": 1, 14 | 15 | // Maximum distance away from center that pieces can spawn. Cannot be greater than 128 16 | "max_distance_from_center": 80, 17 | 18 | // The biome tag to use for what biomes that this structure can spawn in 19 | "biomes": "#structure_tutorial:has_structure/code_structure_sky_fan_biomes", 20 | 21 | // The generation step for when to generate the structure. there are 10 stages you can pick from! 22 | // This surface structure stage places the structure before plants and ores are generated 23 | // See GenerationStep.Feature enum for all the stages you can use and what order they are in 24 | "step": "surface_structures", 25 | 26 | // Where to spawn our structure at what y value if project_start_to_heightmap is not present. 27 | // start_height can be used to spawn at a random fixed y value by doing something like: "max_inclusive": { "below_top": 10 }, "min_inclusive": { "above_bottom": 32 } 28 | // If project_start_to_heightmap is present, the y value chosen in start_height will be added to the terrain's y value. 29 | // So a start height that gives -5 will sink the structure 5 blocks into the terrain. Here, we will spawn this structure 60 blocks above the terrain. 30 | "start_height": { 31 | "absolute": 60 32 | }, 33 | 34 | // Makes our sky fan structure take the terrain's top y value and add it to the start_height y value above. 35 | // The final value is the y value the structures spawn at. 36 | // WORLD_SURFACE_WG will stop at first non-air block so it spawns above oceans always instead of sunken into a deep sea. 37 | "project_start_to_heightmap": "WORLD_SURFACE_WG", 38 | 39 | // Optional thing to control whether the structure will be waterlogged when replacing pre-existing water in the world. 40 | // Set this to "ignore_waterlogging" if you are making underground structures that you do not want to get waterlogged and flooded by aquifiers. 41 | "liquid_settings": "apply_waterlogging", 42 | 43 | // What mobs can spawn over time in the structure. 44 | // Make sure you add the mob to the right category (monster, creature, etc) 45 | "spawn_overrides": { 46 | "monster": { 47 | "bounding_box": "piece", // Use the bounding box of individual pieces instead of the entire structure's bounds. 48 | "spawns": [ 49 | { 50 | "type": "minecraft:evoker", 51 | "weight": 5, 52 | "minCount": 1, 53 | "maxCount": 2 54 | }, 55 | { 56 | "type": "minecraft:phantom", 57 | "weight": 1, 58 | "minCount": 1, 59 | "maxCount": 1 60 | } 61 | ] 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /src/main/resources/data/structure_tutorial/worldgen/structure/json_only_house.json: -------------------------------------------------------------------------------- 1 | { 2 | // The base structure class to use for the behavior of the structure. In this case, minecraft:jigsaw is good for most structures. 3 | "type": "minecraft:jigsaw", 4 | 5 | // the path to the template pool json file to use 6 | "start_pool": "structure_tutorial:run_down_house/start_pool", 7 | 8 | // This is how many pieces away from the starting piece a piece of the structure can spawn 9 | // Think of it like the length of the branch of the structure 10 | "size": 2, 11 | 12 | // Maximum distance away from center that pieces can spawn. Cannot be greater than 128 13 | "max_distance_from_center": 80, 14 | 15 | // The biome tag to use for what biomes that this structure can spawn in" 16 | "biomes": "#structure_tutorial:has_structure/json_only_house_biomes", 17 | 18 | // The generation step for when to generate the structure. there are 10 stages you can pick from! 19 | // This surface structure stage places the structure before plants and ores are generated 20 | // See GenerationStep.Feature enum for all the stages you can use and what order they are in 21 | "step": "surface_structures", 22 | 23 | // This will add land around the bottom of the structure. (Based on the starting piece's y value) 24 | // The kinds of values allowed here are: none, beard_thin, beard_box, and bury 25 | // If no land change is deseried, removed this field entirely. 26 | "terrain_adaptation": "beard_thin", 27 | 28 | // Where to spawn our structure at what y value if project_start_to_heightmap is not present. 29 | // start_height can be used to spawn at a random fixed y value by doing something like: "max_inclusive": { "below_top": 10 }, "min_inclusive": { "above_bottom": 32 } 30 | // If project_start_to_heightmap is present, the y value chosen in start_height will be added to the terrain's y value. 31 | // So a start height that gives -5 will sink the structure 5 blocks into the terrain. 32 | "start_height": { 33 | "absolute": 0 34 | }, 35 | 36 | // Makes our house snap to top land to spawn at. WORLD_SURFACE_WG will stop at first non-air block. 37 | // To get top land at even the bottom of oceans, use OCEAN_FLOOR_WG. 38 | // If no projection desired, remove this field entirely. 39 | "project_start_to_heightmap": "WORLD_SURFACE_WG", 40 | 41 | // Keep this false. This is only for vanilla legacy villages to make it spawn properly. We don't need no hacks here! 42 | "use_expansion_hack": false, 43 | 44 | // Optional thing to control whether the structure will be waterlogged when replacing pre-existing water in the world. 45 | // Set this to "ignore_waterlogging" if you are making underground structures that you do not want to get waterlogged and flooded by aquifiers. 46 | "liquid_settings": "apply_waterlogging", 47 | 48 | // What mobs can spawn over time in the structure. 49 | // Make sure you add the mob to the right category (monster, creature, etc) 50 | "spawn_overrides": { 51 | "monster": { 52 | "bounding_box": "full", // Use the bounding box of entire structure instead of individual pieces. 53 | "spawns": [ 54 | { 55 | "type": "minecraft:illusioner", 56 | "weight": 1, 57 | "minCount": 1, 58 | "maxCount": 2 59 | }, 60 | { 61 | "type": "minecraft:pillager", 62 | "weight": 2, 63 | "minCount": 1, 64 | "maxCount": 4 65 | } 66 | ] 67 | }, 68 | "creature": { 69 | "bounding_box": "piece", 70 | "spawns": [ 71 | { 72 | "type": "minecraft:parrot", 73 | "weight": 1, 74 | "minCount": 1, 75 | "maxCount": 4 76 | } 77 | ] 78 | } 79 | }, 80 | 81 | // Optional thing that allows swapping a template pool with another per structure json instance. More of an expert niche thing. 82 | "pool_aliases": [] 83 | } -------------------------------------------------------------------------------- /src/main/resources/data/structure_tutorial/worldgen/structure_set/code_structure_end_phantom_balloon.json: -------------------------------------------------------------------------------- 1 | { 2 | // What structures to pick to try and spawn if a spot passes the placement check. 3 | // If two or more structures in this list can spawn in a biome at a spot, a random one based on weight is chosen to spawn 4 | "structures": [ 5 | { 6 | "structure": "structure_tutorial:code_structure_end_phantom_balloon", 7 | "weight": 1 8 | } 9 | ], 10 | "placement": { 11 | // The kind of placement to use. Here we use own own custom placement to allow special position selecting. 12 | "type": "structure_tutorial:distance_based_structure_placement", 13 | 14 | // Make sure this is unique and does not match any other structure set's salt 15 | "salt": 895443435, 16 | 17 | // The average distance apart in chunks for spawn attempts 18 | "spacing": 11, 19 | 20 | // Minimum distance apart in chunks for spawn attempts 21 | // MUST ALWAYS BE SMALLER THAN spacing ABOVE 22 | "separation": 6, 23 | 24 | // Our custom field we added to our placement type. 25 | // Ensures this structure spawn 1000 blocks away from world center. 26 | // Prevents our structure from spawning on the Ender Dragon island. 27 | "min_distance_from_world_origin": 1000 28 | } 29 | } -------------------------------------------------------------------------------- /src/main/resources/data/structure_tutorial/worldgen/structure_set/code_structure_sea_boat.json: -------------------------------------------------------------------------------- 1 | { 2 | // What structures to pick to try and spawn if a spot passes the placement check. 3 | // If two or more structures in this list can spawn in a biome at a spot, a random one based on weight is chosen to spawn 4 | "structures": [ 5 | { 6 | "structure": "structure_tutorial:code_structure_sea_boat", 7 | "weight": 1 8 | } 9 | ], 10 | "placement": { 11 | // The kind of placement to use. The other kind is ring based like strongholds use. 12 | "type": "minecraft:random_spread", 13 | 14 | // Make sure this is unique and does not match any other structure set's salt 15 | "salt": 4367768776, 16 | 17 | // The average distance apart in chunks for spawn attempts 18 | "spacing": 14, 19 | 20 | // Minimum distance apart in chunks for spawn attempts 21 | // MUST ALWAYS BE SMALLER THAN spacing ABOVE 22 | "separation": 5 23 | } 24 | } -------------------------------------------------------------------------------- /src/main/resources/data/structure_tutorial/worldgen/structure_set/code_structure_sky_fan.json: -------------------------------------------------------------------------------- 1 | { 2 | // What structures to pick to try and spawn if a spot passes the placement check. 3 | // If two or more structures in this list can spawn in a biome at a spot, a random one based on weight is chosen to spawn 4 | "structures": [ 5 | { 6 | "structure": "structure_tutorial:code_structure_sky_fan", 7 | "weight": 1 8 | } 9 | ], 10 | "placement": { 11 | // The kind of placement to use. The other kind is ring based like strongholds use. 12 | "type": "minecraft:random_spread", 13 | 14 | // Make sure this is unique and does not match any other structure set's salt 15 | "salt": 1224466880, 16 | 17 | // The average distance apart in chunks for spawn attempts 18 | "spacing": 16, 19 | 20 | // Minimum distance apart in chunks for spawn attempts 21 | // MUST ALWAYS BE SMALLER THAN spacing ABOVE 22 | "separation": 6, 23 | 24 | // Prevent spawning near Ocean Monuments 25 | // WARNING: This is a tricky and potentially dangerous setting to use. 26 | // What this is checking for is making sure Sky Fan does not spawn near any point that the 27 | // Ocean Monument structure set sees as a valid POSSIBLE position. It does not check if the monument 28 | // will actually spawn there. It does not even check that the biome is valid for monuments. 29 | // Do not use this with a high chunk count and do not use it targeting a low spacing/separation structure set. 30 | // And most importantly, do not have two structure sets target each other in an exclusion zone or else you will cause an infinite loop. 31 | "exclusion_zone": { 32 | "chunk_count": 4, 33 | "other_set": "minecraft:ocean_monuments" 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/main/resources/data/structure_tutorial/worldgen/structure_set/json_only_house.json: -------------------------------------------------------------------------------- 1 | { 2 | // What structures to pick to try and spawn if a spot passes the placement check. 3 | // If two or more structures in this list can spawn in a biome at a spot, a random one based on weight is chosen to spawn 4 | "structures": [ 5 | { 6 | "structure": "structure_tutorial:json_only_house", 7 | "weight": 1 8 | } 9 | ], 10 | "placement": { 11 | // The kind of placement to use. The other kind is ring based like strongholds use. 12 | "type": "minecraft:random_spread", 13 | 14 | // Make sure this is unique and does not match any other structure set's salt 15 | "salt": 1694767080, 16 | 17 | // The average distance apart in chunks for spawn attempts 18 | "spacing": 12, 19 | 20 | // Minimum distance apart in chunks for spawn attempts 21 | // MUST ALWAYS BE SMALLER THAN spacing ABOVE 22 | "separation": 4 23 | } 24 | } -------------------------------------------------------------------------------- /src/main/resources/data/structure_tutorial/worldgen/template_pool/end_phantom_balloon.json: -------------------------------------------------------------------------------- 1 | { 2 | // More info on template pools can be found here: https://minecraft.gamepedia.com/Custom_world_generation#JSON_format_8 3 | 4 | // The template pool to refer to if the entries in 'elements' fails to generate. 5 | "fallback": "minecraft:empty", 6 | 7 | // Here you can list as many nbt files or placed features to spawn. 8 | // Do note that placed features needs a special element entry stuff. 9 | "elements": [ 10 | { 11 | // How likely out of all the element for this one to be chosen. 12 | "weight": 1, 13 | "element": { 14 | 15 | // The Identifier of the nbt file itself of the structure piece. 16 | // Note, this will automatically check into the 'structures' folder for the nbt file. 17 | // The final path would look like 'resources/data/structure_tutorial/structure/end_phantom_balloon.nbt' 18 | "location": "structure_tutorial:end_phantom_balloon", 19 | "processors": "minecraft:empty", 20 | "projection": "rigid", 21 | "element_type": "minecraft:single_pool_element" 22 | } 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /src/main/resources/data/structure_tutorial/worldgen/template_pool/run_down_house/side_pool.json: -------------------------------------------------------------------------------- 1 | { 2 | // More info on template pools can be found here: https://minecraft.gamepedia.com/Custom_world_generation#JSON_format_8 3 | // Yes, worldgen json files can have comments. Minecraft does "lenient" parsing of these json files. 4 | 5 | "fallback": "minecraft:empty", 6 | "elements": [ 7 | { 8 | "weight": 1, 9 | "element": { 10 | 11 | // This side piece will only spawn if this piece is entirely outside or inside the calling piece's bounding box 12 | // Intersecting pieces is not allowed in Jigsaw structures so plan accordingly! 13 | // To spawn a piece entirely outside a calling piece, make sure the Jigsaw block of the calling 14 | // piece is facing outward on the edge of its bounding box. To spawn a piece entirely contained 15 | // inside a calling piece, have the jigsaw block face inward and that there is enough space in 16 | // the calling piece's boundaries to spawn a smaller inner piece. 17 | 18 | // Also, heads up, the structure_tutorial:run_down_house_left_side piece has a Jigsaw Block 19 | // that is targeting this pool directly. Be sure to change that Jigsaw Block's target pool 20 | // to make this side pool spawn in your own mod. 21 | "location": "structure_tutorial:run_down_house_right_side", 22 | "processors": "minecraft:empty", 23 | "projection": "rigid", 24 | "element_type": "minecraft:single_pool_element" 25 | } 26 | }, 27 | { 28 | 29 | // A second entry here means the starting piece's Jigsaw Block has a 50% chance of spawning 30 | // this golden right side piece instead of the regular right side. Think of it like a lottery. 31 | "weight": 1, 32 | "element": { 33 | "location": "structure_tutorial:run_down_house_right_side_golden", 34 | "processors": "minecraft:empty", 35 | "projection": "rigid", 36 | "element_type": "minecraft:single_pool_element" 37 | } 38 | } 39 | ] 40 | } -------------------------------------------------------------------------------- /src/main/resources/data/structure_tutorial/worldgen/template_pool/run_down_house/start_pool.json: -------------------------------------------------------------------------------- 1 | { 2 | // More info on template pools can be found here: https://minecraft.gamepedia.com/Custom_world_generation#JSON_format_8 3 | // Yes, worldgen json files can have comments. Minecraft does "lenient" parsing of these json files. 4 | 5 | // The template pool to refer to if the entries in 'elements' fails to generate. 6 | "fallback": "minecraft:empty", 7 | 8 | // Here you can list as many nbt files or placed features to spawn. 9 | // Do note that placed features needs a special element entry stuff. 10 | "elements": [ 11 | { 12 | 13 | // How likely out of all the element for this one to be chosen. 14 | "weight": 1, 15 | "element": { 16 | 17 | // The Identifier of the nbt file itself of the structure piece. 18 | // Note, this will automatically check into the 'structures' folder for the nbt file. 19 | // The final path would look like 'resources/data/structure_tutorial/structures/run_down_house_left_side.nbt' 20 | // The Jigsaw block in the structure will point to side_pool.json to spawn the other half of the house. 21 | "location": "structure_tutorial:run_down_house_left_side", 22 | 23 | // Processor JSON files can be used to randomize or replace blocks dynamically. Here, we don't do any of that. 24 | "processors": "minecraft:empty", 25 | 26 | // If set to 'terrain_matching', the house would be deformed to fit the change in land. 27 | // That's best for roads so lets stay 'rigid' for now. 28 | "projection": "rigid", 29 | 30 | // The kind of element we are spawning. This one is most likely what you want. 31 | // There's 'minecraft:legacy_single_pool_element' but that swaps the behavior of 32 | // Air and Structure Void in your piece as well as change the attachment mechanism. 33 | // It's only for old Vanilla Jigsaw Structures so don't use it. Stick with non-legacy. 34 | "element_type": "minecraft:single_pool_element" 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /src/main/resources/data/structure_tutorial/worldgen/template_pool/sea_boat.json: -------------------------------------------------------------------------------- 1 | { 2 | // More info on template pools can be found here: https://minecraft.gamepedia.com/Custom_world_generation#JSON_format_8 3 | 4 | // The template pool to refer to if the entries in 'elements' fails to generate. 5 | "fallback": "minecraft:empty", 6 | 7 | // Here you can list as many nbt files or placed features to spawn. 8 | // Do note that placed features needs a special element entry stuff. 9 | "elements": [ 10 | { 11 | // How likely out of all the element for this one to be chosen. 12 | "weight": 1, 13 | "element": { 14 | 15 | // The Identifier of the nbt file itself of the structure piece. 16 | // Note, this will automatically check into the 'structures' folder for the nbt file. 17 | // The final path would look like 'resources/data/structure_tutorial/structure/sea_boat.nbt' 18 | "location": "structure_tutorial:sea_boat", 19 | "processors": "minecraft:empty", 20 | "projection": "rigid", 21 | "element_type": "minecraft:single_pool_element" 22 | } 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /src/main/resources/data/structure_tutorial/worldgen/template_pool/sky_fan.json: -------------------------------------------------------------------------------- 1 | { 2 | // More info on template pools can be found here: https://minecraft.gamepedia.com/Custom_world_generation#JSON_format_8 3 | 4 | // The template pool to refer to if the entries in 'elements' fails to generate. 5 | "fallback": "minecraft:empty", 6 | 7 | // Here you can list as many nbt files or placed features to spawn. 8 | // Do note that placed features needs a special element entry stuff. 9 | "elements": [ 10 | { 11 | // How likely out of all the element for this one to be chosen. 12 | "weight": 1, 13 | "element": { 14 | 15 | // The Identifier of the nbt file itself of the structure piece. 16 | // Note, this will automatically check into the 'structures' folder for the nbt file. 17 | // The final path would look like 'resources/data/structure_tutorial/structure/sky_fan.nbt' 18 | "location": "structure_tutorial:sky_fan", 19 | 20 | // Specifies to run this processor list json for the above nbt file when placing it in the world 21 | // Processor lists are great for randomizing blocks and making structures appear more natural! 22 | // Note, this will automatically check into the 'worldgen/processor_list' folder for the json file. 23 | // The final path would look like 'resources/data/structure_tutorial/worldgen/processor_list/randomize_stonebrick.json' 24 | "processors": "structure_tutorial:randomize_stonebrick", 25 | "projection": "rigid", 26 | "element_type": "minecraft:single_pool_element" 27 | } 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "description": "StructureTutorialMod resources", 4 | "pack_format": 26, 5 | "_comment": "Happy modding!", 6 | "supported_formats": [0, 1000000] 7 | } 8 | } 9 | --------------------------------------------------------------------------------