├── .github └── workflows │ └── release.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle.kts ├── deploy.sh ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── src ├── main ├── java │ └── org │ │ └── polydev │ │ └── gaea │ │ ├── Debug.java │ │ ├── Gaea.java │ │ ├── GaeaPlugin.java │ │ ├── biome │ │ ├── Biome.java │ │ ├── BiomeGrid.java │ │ ├── Decorator.java │ │ ├── Generator.java │ │ └── NormalizationUtil.java │ │ ├── command │ │ ├── Command.java │ │ ├── DebugCommand.java │ │ ├── PlayerCommand.java │ │ └── WorldCommand.java │ │ ├── generation │ │ ├── GaeaChunkGenerator.java │ │ ├── GenerationPhase.java │ │ └── GenerationPopulator.java │ │ ├── lang │ │ ├── Language.java │ │ ├── Message.java │ │ ├── MultiLineMessage.java │ │ └── SingleLineMessage.java │ │ ├── math │ │ ├── ChunkInterpolator.java │ │ ├── ChunkInterpolator2.java │ │ ├── ChunkInterpolator3.java │ │ ├── FastNoiseLite.java │ │ ├── Interpolator.java │ │ ├── Interpolator3.java │ │ ├── MathUtil.java │ │ ├── ProbabilityCollection.java │ │ └── Range.java │ │ ├── population │ │ ├── ChunkCoordinate.java │ │ ├── GaeaBlockPopulator.java │ │ └── PopulationManager.java │ │ ├── profiler │ │ ├── DataHolder.java │ │ ├── DataType.java │ │ ├── Desire.java │ │ ├── Measurement.java │ │ ├── ProfileFuture.java │ │ └── WorldProfiler.java │ │ ├── serial │ │ └── MovedObjectInputStream.java │ │ ├── structures │ │ ├── NMSStructure.java │ │ ├── Structure.java │ │ ├── StructureUtil.java │ │ ├── UserDefinedStructure.java │ │ ├── features │ │ │ ├── BlockReplaceFeature.java │ │ │ ├── EntityFeature.java │ │ │ ├── Feature.java │ │ │ ├── LootFeature.java │ │ │ └── PersistentDataFeature.java │ │ ├── loot │ │ │ ├── Entry.java │ │ │ ├── LootTable.java │ │ │ ├── Pool.java │ │ │ └── functions │ │ │ │ ├── AmountFunction.java │ │ │ │ ├── DamageFunction.java │ │ │ │ ├── EnchantWithLevelsFunction.java │ │ │ │ └── Function.java │ │ └── spawn │ │ │ ├── AirSpawn.java │ │ │ ├── GroundSpawn.java │ │ │ ├── StructureSpawnInfo.java │ │ │ └── UndergroundSpawn.java │ │ ├── tree │ │ ├── CustomTreeType.java │ │ ├── Tree.java │ │ ├── TreeType.java │ │ └── fractal │ │ │ ├── EntitySpawnHolder.java │ │ │ ├── FractalTree.java │ │ │ ├── TreeGeometry.java │ │ │ ├── TreeGetter.java │ │ │ └── trees │ │ │ ├── Cactus.java │ │ │ ├── IceSpike.java │ │ │ ├── OakTree.java │ │ │ ├── Rock.java │ │ │ ├── ShatteredPillar.java │ │ │ ├── ShatteredTree.java │ │ │ ├── SmallShatteredPillar.java │ │ │ ├── SmallShatteredTree.java │ │ │ └── SpruceTree.java │ │ ├── util │ │ ├── FastRandom.java │ │ ├── GlueList.java │ │ ├── JarUtil.java │ │ ├── SerializationUtil.java │ │ └── WorldUtil.java │ │ └── world │ │ ├── Flora.java │ │ ├── FloraType.java │ │ ├── Ore.java │ │ ├── carving │ │ ├── Carver.java │ │ └── Worm.java │ │ └── palette │ │ ├── Palette.java │ │ ├── RandomPalette.java │ │ └── SimplexPalette.java └── resources │ ├── config.yml │ └── plugin.yml └── test └── java ├── CacheTest.java ├── CellularNoiseLookupTest.java ├── Interp3Test.java ├── PaletteTest.java └── RandomTest.java /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 3 | 4 | name: Java CI with Gradle 5 | 6 | on: 7 | push: 8 | # tags: 9 | # - "v*" # Push events to matching v*, i.e. v1.0, v20.15.10 10 | 11 | jobs: 12 | build: 13 | 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Set up JDK 1.8 19 | uses: actions/setup-java@v1 20 | with: 21 | java-version: 1.8 22 | - name: Pull Maven Cache 23 | uses: actions/cache@v2 24 | id: maven-cache 25 | with: 26 | path: ~/.m2/repository 27 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 28 | - name: Build Gaea 29 | run: gradle shadowJar 30 | - name: Publish release 31 | uses: marvinpinto/action-automatic-releases@latest 32 | with: 33 | repo_token: ${{ secrets.GITHUB_TOKEN }} 34 | automatic_release_tag: latest 35 | prerelease: false 36 | title: "Latest Build" 37 | files: | 38 | build/libs/Gaea-*.jar -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Maven template 2 | target/ 3 | pom.xml.tag 4 | pom.xml.releaseBackup 5 | pom.xml.versionsBackup 6 | pom.xml.next 7 | release.properties 8 | dependency-reduced-pom.xml 9 | buildNumber.properties 10 | .mvn/timing.properties 11 | # https://github.com/takari/maven-wrapper#usage-without-binary-jar 12 | .mvn/wrapper/maven-wrapper.jar 13 | 14 | ### Gradle template 15 | .gradle 16 | **/build/ 17 | !src/**/build/ 18 | 19 | # Ignore Gradle GUI config 20 | gradle-app.setting 21 | 22 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 23 | !gradle-wrapper.jar 24 | 25 | # Cache of project 26 | .gradletasknamecache 27 | 28 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 29 | # gradle/wrapper/gradle-wrapper.properties 30 | 31 | ### Java template 32 | # Compiled class file 33 | *.class 34 | 35 | # Log file 36 | *.log 37 | 38 | # BlueJ files 39 | *.ctxt 40 | 41 | # Mobile Tools for Java (J2ME) 42 | .mtj.tmp/ 43 | 44 | # Package Files # 45 | *.jar 46 | *.war 47 | *.nar 48 | *.ear 49 | *.zip 50 | *.tar.gz 51 | *.rar 52 | 53 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 54 | hs_err_pid* 55 | 56 | ### JetBrains template 57 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 58 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 59 | 60 | # User-specific stuff 61 | .idea/**/workspace.xml 62 | .idea/**/tasks.xml 63 | .idea/**/usage.statistics.xml 64 | .idea/**/dictionaries 65 | .idea/**/shelf 66 | 67 | # Generated files 68 | .idea/**/contentModel.xml 69 | 70 | # Sensitive or high-churn files 71 | .idea/**/dataSources/ 72 | .idea/**/dataSources.ids 73 | .idea/**/dataSources.local.xml 74 | .idea/**/sqlDataSources.xml 75 | .idea/**/dynamic.xml 76 | .idea/**/uiDesigner.xml 77 | .idea/**/dbnavigator.xml 78 | 79 | # Gradle 80 | .idea/**/gradle.xml 81 | .idea/**/libraries 82 | 83 | # Gradle and Maven with auto-import 84 | # When using Gradle or Maven with auto-import, you should exclude module files, 85 | # since they will be recreated, and may cause churn. Uncomment if using 86 | # auto-import. 87 | .idea/artifacts 88 | .idea/compiler.xml 89 | .idea/jarRepositories.xml 90 | .idea/modules.xml 91 | .idea/*.iml 92 | .idea/modules 93 | *.iml 94 | *.ipr 95 | 96 | # CMake 97 | cmake-build-*/ 98 | 99 | # Mongo Explorer plugin 100 | .idea/**/mongoSettings.xml 101 | 102 | # File-based project format 103 | *.iws 104 | 105 | # IntelliJ 106 | out/ 107 | 108 | # mpeltonen/sbt-idea plugin 109 | .idea_modules/ 110 | 111 | # JIRA plugin 112 | atlassian-ide-plugin.xml 113 | 114 | # Cursive Clojure plugin 115 | .idea/replstate.xml 116 | 117 | # Crashlytics plugin (for Android Studio and IntelliJ) 118 | com_crashlytics_export_strings.xml 119 | crashlytics.properties 120 | crashlytics-build.properties 121 | fabric.properties 122 | 123 | # Editor-based Rest Client 124 | .idea/httpRequests 125 | 126 | # Android studio 3.1+ serialized cache file 127 | .idea/caches/build_file_checksums.ser 128 | 129 | # Ignore Gradle build output directory 130 | build 131 | /target/ 132 | 133 | .idea/sonarlint/** -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gaea 2 | Gaea world generation API 3 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar 2 | import java.io.ByteArrayOutputStream 3 | 4 | plugins { 5 | java 6 | id("maven-publish") 7 | id("com.github.johnrengelman.shadow").version("6.0.0") 8 | } 9 | 10 | repositories { 11 | flatDir { dirs("lib") } 12 | maven { url = uri("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") } 13 | maven { url = uri("http://maven.enginehub.org/repo/") } 14 | maven { url = uri("https://repo.codemc.org/repository/maven-public") } 15 | maven { url = uri("https://papermc.io/repo/repository/maven-public/") } 16 | maven { url = uri("https://repo.aikar.co/content/groups/aikar/") } 17 | } 18 | 19 | java { 20 | sourceCompatibility = JavaVersion.VERSION_1_8 21 | targetCompatibility = JavaVersion.VERSION_1_8 22 | } 23 | 24 | group = "org.polydev.gaea" 25 | val versionObj = Version("1", "15", "0", false) 26 | version = versionObj 27 | 28 | dependencies { 29 | compileOnly("org.jetbrains:annotations:20.1.0") // more recent. 30 | compileOnly("org.spigotmc:spigot-api:1.16.4-R0.1-SNAPSHOT") 31 | implementation("com.googlecode.json-simple:json-simple:1.1") 32 | implementation("commons-io:commons-io:2.4") 33 | implementation("org.apache.commons:commons-rng-core:1.3") 34 | implementation("net.jafama:jafama:2.3.2") 35 | implementation("co.aikar:taskchain-bukkit:3.7.2") 36 | implementation("com.esotericsoftware:reflectasm:1.11.9") 37 | implementation("org.bstats:bstats-bukkit:1.7") 38 | 39 | // JUnit. 40 | testImplementation("org.junit.jupiter:junit-jupiter-api:5.7.0") 41 | testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.7.0") 42 | 43 | testImplementation("org.spigotmc:spigot-api:1.16.4-R0.1-SNAPSHOT") 44 | } 45 | 46 | tasks.test { 47 | useJUnitPlatform() 48 | 49 | maxHeapSize = "1G" 50 | ignoreFailures = false 51 | failFast = true 52 | maxParallelForks = 12 53 | } 54 | 55 | 56 | tasks.withType { 57 | archiveClassifier.set("") 58 | archiveBaseName.set("Gaea") 59 | setVersion(project.version) 60 | relocate("org.apache.commons", "org.polydev.gaea.libs.commons") 61 | relocate("org.bstats.bukkit", "org.polydev.gaea.libs.bstats") 62 | relocate("co.aikar.taskchain", "org.polydev.gaea.libs.taskchain") 63 | relocate("com.esotericsoftware", "org.polydev.gaea.libs.reflectasm") 64 | relocate("net.jafama", "org.polydev.gaea.libs.jafama") 65 | } 66 | 67 | val sourcesJar by tasks.registering(Jar::class) { 68 | classifier = "sources" 69 | from(sourceSets.main.get().allSource) 70 | } 71 | 72 | publishing { 73 | publications { 74 | create("mavenJava") { 75 | artifact(tasks["sourcesJar"]) 76 | artifact(tasks["jar"]) 77 | } 78 | } 79 | 80 | repositories { 81 | val mavenUrl = "https://repo.codemc.io/repository/maven-releases/" 82 | val mavenSnapshotUrl = "https://repo.codemc.io/repository/maven-snapshots/" 83 | 84 | maven((if (versionObj.preRelease) mavenSnapshotUrl else mavenUrl)) { 85 | val mavenUsername: String? by project 86 | val mavenPassword: String? by project 87 | if (mavenUsername != null && mavenPassword != null) { 88 | credentials { 89 | username = mavenUsername 90 | password = mavenPassword 91 | } 92 | } 93 | } 94 | } 95 | } 96 | 97 | /** 98 | * Version class that does version stuff. 99 | */ 100 | class Version(val major: String, val minor: String, val revision: String, val preRelease: Boolean = false) { 101 | 102 | override fun toString(): String { 103 | return if (preRelease) 104 | "$major.$minor.$revision-BETA+${getGitHash()}" 105 | else //Only use git hash if it's a prerelease. 106 | "$major.$minor.$revision" 107 | } 108 | } 109 | 110 | fun getGitHash(): String { 111 | val stdout = ByteArrayOutputStream() 112 | exec { 113 | commandLine = mutableListOf("git", "rev-parse", "--short", "HEAD") 114 | standardOutput = stdout 115 | } 116 | return stdout.toString().trim() 117 | } -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | cp target/prod/Gaea.jar ../Terra/target/server/plugins/Gaea.jar 2 | cp target/prod/Gaea.jar ../BetterEnd/target/server/plugins/Gaea.jar 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PolyhedralDev/Gaea/89e02473cf0ad875689237695afd9d262408880e/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-6.7-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or 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 UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ]; do 30 | ls=$(ls -ld "$PRG") 31 | link=$(expr "$ls" : '.*-> \(.*\)$') 32 | if expr "$link" : '/.*' >/dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=$(dirname "$PRG")"/$link" 36 | fi 37 | done 38 | SAVED="$(pwd)" 39 | cd "$(dirname \"$PRG\")/" >/dev/null 40 | APP_HOME="$(pwd -P)" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=$(basename "$0") 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn() { 53 | echo "$*" 54 | } 55 | 56 | die() { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "$(uname)" in 69 | CYGWIN*) 70 | cygwin=true 71 | ;; 72 | Darwin*) 73 | darwin=true 74 | ;; 75 | MINGW*) 76 | msys=true 77 | ;; 78 | NONSTOP*) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ]; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ]; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ]; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ]; then 109 | MAX_FD_LIMIT=$(ulimit -H -n) 110 | if [ $? -eq 0 ]; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ]; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ]; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ]; then 130 | APP_HOME=$(cygpath --path --mixed "$APP_HOME") 131 | CLASSPATH=$(cygpath --path --mixed "$CLASSPATH") 132 | 133 | JAVACMD=$(cygpath --unix "$JAVACMD") 134 | 135 | # We build the pattern for arguments to be converted via cygpath 136 | ROOTDIRSRAW=$(find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null) 137 | SEP="" 138 | for dir in $ROOTDIRSRAW; do 139 | ROOTDIRS="$ROOTDIRS$SEP$dir" 140 | SEP="|" 141 | done 142 | OURCYGPATTERN="(^($ROOTDIRS))" 143 | # Add a user-defined pattern to the cygpath arguments 144 | if [ "$GRADLE_CYGPATTERN" != "" ]; then 145 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 146 | fi 147 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 148 | i=0 149 | for arg in "$@"; do 150 | CHECK=$(echo "$arg" | egrep -c "$OURCYGPATTERN" -) 151 | CHECK2=$(echo "$arg" | egrep -c "^-") ### Determine if an option 152 | 153 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ]; then ### Added a condition 154 | eval $(echo args$i)=$(cygpath --path --ignore --mixed "$arg") 155 | else 156 | eval $(echo args$i)="\"$arg\"" 157 | fi 158 | i=$(expr $i + 1) 159 | done 160 | case $i in 161 | 0) set -- ;; 162 | 1) set -- "$args0" ;; 163 | 2) set -- "$args0" "$args1" ;; 164 | 3) set -- "$args0" "$args1" "$args2" ;; 165 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 166 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 167 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 168 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 169 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 170 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 171 | esac 172 | fi 173 | 174 | # Escape application args 175 | save() { 176 | for i; do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/"; done 177 | echo " " 178 | } 179 | APP_ARGS=$(save "$@") 180 | 181 | # Collect all arguments for the java command, following the shell quoting and substitution rules 182 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 183 | 184 | exec "$JAVACMD" "$@" 185 | -------------------------------------------------------------------------------- /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 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | 88 | @rem Execute Gradle 89 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 90 | 91 | :end 92 | @rem End local scope for the variables with windows NT shell 93 | if "%ERRORLEVEL%"=="0" goto mainEnd 94 | 95 | :fail 96 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 97 | rem the _cmd.exe /c_ return code! 98 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 99 | exit /b 1 100 | 101 | :mainEnd 102 | if "%OS%"=="Windows_NT" endlocal 103 | 104 | :omega 105 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * 6 | * Detailed information about configuring a multi-project build in Gradle can be found 7 | * in the user manual at https://docs.gradle.org/6.6.1/userguide/multi_project_builds.html 8 | */ 9 | 10 | rootProject.name = "Gaea" 11 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/Debug.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea; 2 | 3 | import org.bukkit.plugin.java.JavaPlugin; 4 | 5 | public class Debug { 6 | public static JavaPlugin main; 7 | 8 | public static void setMain(JavaPlugin main) { 9 | Debug.main = main; 10 | } 11 | 12 | public static void info(String message) { 13 | if(Gaea.isDebug()) main.getLogger().info(message); 14 | } 15 | public static void warn(String message) { 16 | if(Gaea.isDebug()) main.getLogger().warning(message); 17 | } 18 | 19 | public static void error(String message) { 20 | if(Gaea.isDebug()) main.getLogger().severe(message); 21 | } 22 | 23 | public static void stack(Exception e) { 24 | if(Gaea.isDebug()) e.printStackTrace(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/Gaea.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea; 2 | 3 | import org.bstats.bukkit.Metrics; 4 | import org.bukkit.World; 5 | import org.bukkit.configuration.file.FileConfiguration; 6 | import org.bukkit.plugin.java.JavaPlugin; 7 | 8 | import java.io.File; 9 | 10 | public class Gaea extends JavaPlugin { 11 | private static boolean debug; 12 | 13 | @Override 14 | public void onDisable() { 15 | super.onDisable(); 16 | } 17 | 18 | @Override 19 | public void onEnable() { 20 | super.onEnable(); 21 | Metrics metrics = new Metrics(this, 9092); 22 | saveDefaultConfig(); 23 | reloadConfig(); 24 | FileConfiguration configuration = getConfig(); 25 | debug = configuration.getBoolean("debug", false); 26 | } 27 | 28 | public static File getGaeaFolder(World w) { 29 | File f = new File(w.getWorldFolder(), "gaea"); 30 | f.mkdirs(); 31 | return f; 32 | } 33 | 34 | public static boolean isDebug() { 35 | return debug; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/GaeaPlugin.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea; 2 | 3 | import org.bukkit.plugin.java.JavaPlugin; 4 | import org.polydev.gaea.generation.GaeaChunkGenerator; 5 | import org.polydev.gaea.lang.Language; 6 | 7 | public abstract class GaeaPlugin extends JavaPlugin { 8 | public abstract boolean isDebug(); 9 | public abstract Class getGeneratorClass(); 10 | public abstract Language getLanguage(); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/biome/Biome.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.biome; 2 | 3 | import org.bukkit.World; 4 | import org.polydev.gaea.structures.features.Feature; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Interface to be implemented by a custom generator's Biome enum.
10 | * Represents a custom biome, and contains methods to retrieve information about each type. 11 | */ 12 | public interface Biome { 13 | 14 | /** 15 | * Gets the Vanilla biome to represent the custom biome. 16 | * 17 | * @return Biome - The Vanilla biome. 18 | */ 19 | org.bukkit.block.Biome getVanillaBiome(); 20 | 21 | /** 22 | * Gets the BiomeTerrain instance used to generate the biome. 23 | * 24 | * @return BiomeTerrain - The terrain generation instance. 25 | */ 26 | Generator getGenerator(); 27 | 28 | /** 29 | * Gets a list of Structure Features to be applied to all structures in the biome. 30 | * 31 | * @return List<Feature> - The list of Features. 32 | */ 33 | List getStructureFeatures(); 34 | 35 | /** 36 | * Returns the Decorator instance containing information about the population in the biome. 37 | * 38 | * @return Decorator - the Decorator instance. 39 | */ 40 | Decorator getDecorator(); 41 | 42 | /** 43 | * Gets the BiomeTerrain instance used to generate the biome in this world. 44 | * 45 | * @return BiomeTerrain - The terrain generation instance. 46 | */ 47 | default Generator getGenerator(World w) { 48 | return getGenerator(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/biome/BiomeGrid.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.biome; 2 | 3 | import org.bukkit.Location; 4 | import org.bukkit.World; 5 | import org.polydev.gaea.generation.GenerationPhase; 6 | import org.polydev.gaea.math.FastNoiseLite; 7 | 8 | public abstract class BiomeGrid { 9 | private final FastNoiseLite noiseX; 10 | private final FastNoiseLite noiseZ; 11 | private final World world; 12 | private final int sizeX; 13 | private final int sizeZ; 14 | private Biome[][] grid; 15 | 16 | 17 | public BiomeGrid(World w, double freq1, double freq2, int sizeX, int sizeZ) { 18 | this.sizeX = sizeX; 19 | this.sizeZ = sizeZ; 20 | this.world = w; 21 | this.noiseX = new FastNoiseLite((int) w.getSeed()); 22 | this.noiseZ = new FastNoiseLite((int) w.getSeed() + 1); 23 | this.noiseX.setNoiseType(FastNoiseLite.NoiseType.OpenSimplex2); 24 | this.noiseX.setFractalType(FastNoiseLite.FractalType.FBm); 25 | this.noiseX.setFractalOctaves(4); 26 | this.noiseZ.setNoiseType(FastNoiseLite.NoiseType.OpenSimplex2); 27 | this.noiseZ.setFractalType(FastNoiseLite.FractalType.FBm); 28 | this.noiseZ.setFractalOctaves(4); 29 | this.noiseX.setFrequency(freq1); 30 | this.noiseZ.setFrequency(freq2); 31 | } 32 | 33 | 34 | /** 35 | * Gets the biome at a pair of coordinates. 36 | * 37 | * @param x - X-coordinate at which to fetch biome 38 | * @param z - Z-coordinate at which to fetch biome 39 | * @return Biome - Biome at the given coordinates. 40 | */ 41 | public Biome getBiome(int x, int z, GenerationPhase phase) { 42 | return grid[getBiomeNoiseX(x, z)][getBiomeNoiseZ(x, z)]; 43 | } 44 | 45 | /** 46 | * Gets the biome at a location. 47 | * 48 | * @param l - The location at which to fetch the biome. 49 | * @return Biome - Biome at the given coordinates. 50 | */ 51 | public Biome getBiome(Location l) { 52 | return getBiome(l, GenerationPhase.POST_GEN); 53 | } 54 | 55 | public double[] getRawNoise(int x, int z) { 56 | return new double[] {noiseX.getNoise(x, z), noiseZ.getNoise(x, z)}; 57 | } 58 | 59 | /** 60 | * Get the raw X-noise for coordinates in the Grid. 61 | * 62 | * @param x X coordinate 63 | * @param z Z coordinate 64 | * @return Normalized noise 65 | */ 66 | public int getBiomeNoiseX(int x, int z) { 67 | return normalize(noiseX.getNoise(x, z), sizeX); 68 | } 69 | 70 | /** 71 | * Get the raw Z-noise for coordinates in the Grid. 72 | * 73 | * @param x X coordinate 74 | * @param z Z coordinate 75 | * @return Normalized noise 76 | */ 77 | public int getBiomeNoiseZ(int x, int z) { 78 | return normalize(noiseZ.getNoise(x, z), sizeZ); 79 | } 80 | 81 | public Biome[][] getGrid() { 82 | return grid; 83 | } 84 | 85 | public void setGrid(Biome[][] grid) { 86 | if(grid.length != sizeX) throw new IllegalArgumentException("Invalid length for grid, expected " + sizeX + ", got " + grid.length); 87 | for(Biome[] gridLayer : grid) { 88 | if(gridLayer.length != sizeZ) throw new IllegalArgumentException("Invalid length for grid layer, expected " + sizeZ + ", got " + gridLayer.length); 89 | } 90 | this.grid = grid; 91 | } 92 | 93 | public Biome getBiome(Location l, GenerationPhase phase) { 94 | double biomeNoise = noiseX.getNoise(l.getBlockX(), l.getBlockZ()); 95 | double climateNoise = noiseZ.getNoise(l.getBlockX(), l.getBlockZ()); 96 | return grid[normalize(biomeNoise, sizeX)][normalize(climateNoise, sizeZ)]; 97 | } 98 | 99 | public World getWorld() { 100 | return world; 101 | } 102 | 103 | public int getSizeX() { 104 | return sizeX; 105 | } 106 | 107 | public int getSizeZ() { 108 | return sizeZ; 109 | } 110 | 111 | /** 112 | * Takes a noise input and normalizes it to a value between 0 and 15 inclusive. 113 | * 114 | * @param i - The noise value to normalize. 115 | * @return int - The normalized value. 116 | */ 117 | protected int normalize(double i, int range) { 118 | return NormalizationUtil.normalize(i, range, 4); 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/biome/Decorator.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.biome; 2 | 3 | import org.bukkit.World; 4 | import org.polydev.gaea.math.ProbabilityCollection; 5 | import org.polydev.gaea.structures.Structure; 6 | import org.polydev.gaea.tree.Tree; 7 | import org.polydev.gaea.world.Flora; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | public abstract class Decorator { 13 | private final Map> worldStructureProb = new HashMap<>(); 14 | 15 | public ProbabilityCollection getStructures(World w) { 16 | return worldStructureProb.containsKey(w.getName()) ? worldStructureProb.get(w.getName()) : new ProbabilityCollection<>(); 17 | } 18 | 19 | public abstract ProbabilityCollection getTrees(); 20 | 21 | public abstract int getTreeDensity(); 22 | 23 | public abstract boolean overrideStructureChance(); 24 | 25 | public abstract org.bukkit.block.Biome getVanillaBiome(); 26 | 27 | public abstract ProbabilityCollection getFlora(); 28 | 29 | public abstract int getFloraChance(); 30 | 31 | /** 32 | * Sets the structures that are to be generated in a world. Intended to be invoked during subclass instantiation, or by a configuration class. 33 | * 34 | * @param structures ProbabilityCollection of Structures 35 | * @param w World in which the structures are to be generated. 36 | */ 37 | public void setStructures(ProbabilityCollection structures, World w) { 38 | worldStructureProb.put(w.getName(), structures); 39 | } 40 | 41 | /** 42 | * Sets the structures that are to be generated in a world. Intended to be invoked during subclass instantiation, or by a configuration class. 43 | * 44 | * @param structures ProbabilityCollection of Structures 45 | * @param w Name of world in which the structures are to be generated. 46 | */ 47 | public void setStructures(ProbabilityCollection structures, String w) { 48 | worldStructureProb.put(w, structures); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/biome/Generator.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.biome; 2 | 3 | import org.bukkit.World; 4 | import org.bukkit.block.data.BlockData; 5 | import org.polydev.gaea.math.FastNoiseLite; 6 | import org.polydev.gaea.math.Interpolator; 7 | import org.polydev.gaea.world.palette.Palette; 8 | 9 | public abstract class Generator { 10 | /** 11 | * Gets the 2D noise at a pair of coordinates using the provided FastNoiseLite instance. 12 | * 13 | * @param gen - The FastNoiseLite instance to use. 14 | * @param x - The x coordinate. 15 | * @param z - The z coordinate. 16 | * @return double - Noise value at the specified coordinates. 17 | */ 18 | public abstract double getNoise(FastNoiseLite gen, World w, int x, int z); 19 | 20 | /** 21 | * Gets the 3D noise at a pair of coordinates using the provided FastNoiseLite instance. 22 | * 23 | * @param gen - The FastNoiseLite instance to use. 24 | * @param x - The x coordinate. 25 | * @param y - The y coordinate. 26 | * @param z - The z coordinate. 27 | * @return double - Noise value at the specified coordinates. 28 | */ 29 | public abstract double getNoise(FastNoiseLite gen, World w, int x, int y, int z); 30 | 31 | /** 32 | * Gets the BlocPalette to generate the biome with. 33 | * 34 | * @return BlocPalette - The biome's palette. 35 | */ 36 | public abstract Palette getPalette(int y); 37 | 38 | /** 39 | * Returns true if the biome should be interpolated just once, false to use advanced interpolation + blending. 40 | * @return Whether biome should use minimal interpolation 41 | */ 42 | public abstract boolean useMinimalInterpolation(); 43 | 44 | 45 | /** 46 | * Get the type of interpolation to use in this biome. 47 | * @return Interpolation type 48 | */ 49 | public Interpolator.Type getInterpolationType() { 50 | return Interpolator.Type.LINEAR; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/command/Command.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.command; 2 | 3 | import org.bukkit.command.CommandExecutor; 4 | import org.bukkit.command.CommandSender; 5 | import org.bukkit.command.TabCompleter; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | import org.polydev.gaea.GaeaPlugin; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Arrays; 12 | import java.util.List; 13 | 14 | /** 15 | * Represents a command or subcommand, can be nested via getSubCommands. 16 | */ 17 | public abstract class Command implements CommandExecutor, TabCompleter { 18 | private final GaeaPlugin main; 19 | public Command(GaeaPlugin main) { 20 | this.main = main; 21 | } 22 | public Command(Command parent) { 23 | main = parent.getMain(); 24 | } 25 | 26 | public GaeaPlugin getMain() { 27 | return main; 28 | } 29 | 30 | /** 31 | * Gets the name of the command/subcommand 32 | * @return Name of command 33 | */ 34 | public abstract String getName(); 35 | 36 | /** 37 | * Gets a list of subcommands 38 | * @return List of subcommands 39 | */ 40 | public abstract List getSubCommands(); 41 | 42 | /** 43 | * Executes the given command, returning its success. 44 | *
45 | * If false is returned, then the "usage" plugin.yml entry for this command 46 | * (if defined) will be sent to the player. 47 | * 48 | * @param sender Source of the command 49 | * @param command Command which was executed 50 | * @param label Alias of the command which was used 51 | * @param args Passed command arguments 52 | * @return true if a valid command, otherwise false 53 | */ 54 | 55 | public abstract boolean execute(@NotNull CommandSender sender, @NotNull org.bukkit.command.Command command, @NotNull String label, @NotNull String[] args); 56 | /** 57 | * Gets the number of arguments 58 | * @return Number of arguments 59 | */ 60 | public abstract int arguments(); 61 | 62 | /** 63 | * Executes the given command, invoking subcommands if applicable and returning its success. 64 | *
65 | * If false is returned, then the "usage" plugin.yml entry for this command 66 | * (if defined) will be sent to the player. 67 | * 68 | * @param sender Source of the command 69 | * @param command Command which was executed 70 | * @param label Alias of the command which was used 71 | * @param args Passed command arguments 72 | * @return true if a valid command, otherwise false 73 | */ 74 | @Override 75 | public final boolean onCommand(@NotNull CommandSender sender, @NotNull org.bukkit.command.Command command, @NotNull String label, @NotNull String[] args) { 76 | if(this instanceof DebugCommand && ! main.isDebug()) { 77 | main.getLanguage().send("command.debug-only", sender); 78 | return true; 79 | } 80 | if(args.length > 0) { 81 | for(Command c : getSubCommands()) { 82 | if(c.getName().equals(args[0])) { 83 | return c.onCommand(sender, command, label, Arrays.stream(args, 1, args.length).toArray(String[]::new)); 84 | } 85 | } 86 | if(args.length != arguments()) { 87 | main.getLanguage().send("command.invalid", sender, String.valueOf(arguments()), String.valueOf(args.length)); 88 | return true; 89 | } 90 | return execute(sender, command, label, args); 91 | } 92 | if(args.length != arguments()) { 93 | main.getLanguage().send("command.invalid", sender, String.valueOf(arguments()), String.valueOf(args.length)); 94 | return true; 95 | } 96 | return execute(sender, command, label, new String[] {}); 97 | } 98 | 99 | public abstract List getTabCompletions(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args); 100 | 101 | @Override 102 | public final @Nullable List onTabComplete(@NotNull CommandSender sender, org.bukkit.command.@NotNull Command command, @NotNull String alias, @NotNull String[] args) { 103 | List complete = new ArrayList<>(); 104 | if(args.length > 0) for(Command c : getSubCommands()) { 105 | if(c.getName().startsWith(args[0])) complete.add(c.getName()); 106 | if(c.getName().equals(args[0])) return c.onTabComplete(sender, command, alias, Arrays.stream(args, 1, args.length).toArray(String[]::new)); 107 | } 108 | complete.addAll(getTabCompletions(sender, alias, args)); 109 | return complete; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/command/DebugCommand.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.command; 2 | 3 | /** 4 | * Implementing this interface marks a command as debug-only. 5 | * If a parent command implements this interface, all child commands will be considered debug commands, regardless of whether they implement DebugCommand as well. 6 | */ 7 | public interface DebugCommand { 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/command/PlayerCommand.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.command; 2 | 3 | import org.bukkit.command.CommandSender; 4 | import org.bukkit.entity.Player; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | /** 8 | * A command that may only be executed by a player. If executor is not a player, a message will be displayed and no action will be performed. 9 | */ 10 | public abstract class PlayerCommand extends Command { 11 | public PlayerCommand(Command parent) { 12 | super(parent); 13 | } 14 | 15 | /** 16 | * Executes the given command, returning its success. 17 | *
18 | * If false is returned, then the "usage" plugin.yml entry for this command 19 | * (if defined) will be sent to the player. 20 | * 21 | * @param sender Source of the command 22 | * @param command Command which was executed 23 | * @param label Alias of the command which was used 24 | * @param args Passed command arguments 25 | * @return true if a valid command, otherwise false 26 | */ 27 | @Override 28 | public final boolean execute(@NotNull CommandSender sender, org.bukkit.command.@NotNull Command command, @NotNull String label, @NotNull String[] args) { 29 | if(!(sender instanceof Player)) { 30 | getMain().getLanguage().send("command.players-only", sender); 31 | return true; 32 | } 33 | Player p = (Player) sender; 34 | return execute(p, command, label, args); 35 | } 36 | /** 37 | * Executes the given command, returning its success. 38 | *
39 | * If false is returned, then the "usage" plugin.yml entry for this command 40 | * (if defined) will be sent to the player. 41 | * 42 | * @param sender Player that executed command 43 | * @param command Command which was executed 44 | * @param label Alias of the command which was used 45 | * @param args Passed command arguments 46 | * @return true if a valid command, otherwise false 47 | */ 48 | public abstract boolean execute(@NotNull Player sender, org.bukkit.command.@NotNull Command command, @NotNull String label, @NotNull String[] args); 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/command/WorldCommand.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.command; 2 | 3 | import org.bukkit.World; 4 | import org.bukkit.command.Command; 5 | import org.bukkit.entity.Player; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.polydev.gaea.generation.GaeaChunkGenerator; 8 | 9 | /** 10 | * A command that must be executed by a player, in a Terra world. 11 | */ 12 | public abstract class WorldCommand extends PlayerCommand { 13 | public WorldCommand(org.polydev.gaea.command.Command parent) { 14 | super(parent); 15 | } 16 | 17 | /** 18 | * Executes the given command, returning its success. 19 | *
20 | * If false is returned, then the "usage" plugin.yml entry for this command 21 | * (if defined) will be sent to the player. 22 | * 23 | * @param sender Source of the command 24 | * @param command Command which was executed 25 | * @param label Alias of the command which was used 26 | * @param args Passed command arguments 27 | * @return true if a valid command, otherwise false 28 | */ 29 | @Override 30 | public final boolean execute(@NotNull Player sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { 31 | Class clazz = getMain().getGeneratorClass(); 32 | if(clazz.isInstance(sender.getWorld().getGenerator())) { 33 | return execute(sender, command, label, args, sender.getWorld()); 34 | } else { 35 | getMain().getLanguage().send("command.world", sender); 36 | } 37 | return true; 38 | } 39 | 40 | /** 41 | * Executes the given command, returning its success. 42 | *
43 | * If false is returned, then the "usage" plugin.yml entry for this command 44 | * (if defined) will be sent to the player. 45 | * 46 | * @param sender Player that executed command 47 | * @param command Command which was executed 48 | * @param label Alias of the command which was used 49 | * @param args Passed command arguments 50 | * @param world World in which command was executed 51 | * @return true if a valid command, otherwise false 52 | */ 53 | public abstract boolean execute(@NotNull Player sender, @NotNull Command command, @NotNull String label, @NotNull String[] args, World world); 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/generation/GaeaChunkGenerator.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.generation; 2 | 3 | import org.bukkit.World; 4 | import org.bukkit.generator.ChunkGenerator; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.polydev.gaea.biome.Biome; 7 | import org.polydev.gaea.math.ChunkInterpolator; 8 | import org.polydev.gaea.math.FastNoiseLite; 9 | import org.polydev.gaea.profiler.ProfileFuture; 10 | import org.polydev.gaea.profiler.WorldProfiler; 11 | 12 | import java.util.List; 13 | import java.util.Random; 14 | 15 | public abstract class GaeaChunkGenerator extends ChunkGenerator { 16 | private final ChunkInterpolator.InterpolationType interpolationType; 17 | private FastNoiseLite gen; 18 | private WorldProfiler profiler; 19 | 20 | public GaeaChunkGenerator(ChunkInterpolator.InterpolationType type) { 21 | interpolationType = type; 22 | } 23 | 24 | @Override 25 | public @NotNull ChunkData generateChunkData(@NotNull World world, @NotNull Random random, int chunkX, int chunkZ, @NotNull BiomeGrid biome) { 26 | try(ProfileFuture ignore = measure("TotalChunkGenTime")) { 27 | if(gen == null) { 28 | gen = new FastNoiseLite((int) world.getSeed()); 29 | gen.setNoiseType(FastNoiseLite.NoiseType.OpenSimplex2); 30 | gen.setFractalType(FastNoiseLite.FractalType.FBm); 31 | gen.setFractalOctaves(getNoiseOctaves(world)); 32 | gen.setFrequency(getNoiseFrequency(world)); 33 | } 34 | ChunkData chunk; 35 | ChunkInterpolator interp; 36 | try(ProfileFuture ignored = measure("ChunkBaseGenTime")) { 37 | interp = interpolationType.getInstance(world, chunkX, chunkZ, this.getBiomeGrid(world), gen); 38 | chunk = generateBase(world, random, chunkX, chunkZ, interp); 39 | } 40 | try(ProfileFuture ignored = measure("BiomeApplyTime")) { 41 | org.polydev.gaea.biome.BiomeGrid grid = getBiomeGrid(world); 42 | int xOrig = (chunkX << 4); 43 | int zOrig = (chunkZ << 4); 44 | for(byte x = 0; x < 4; x++) { 45 | for(byte z = 0; z < 4; z++) { 46 | int cx = xOrig + (x << 2); 47 | int cz = zOrig + (z << 2); 48 | Biome b = grid.getBiome(cx, cz, GenerationPhase.PALETTE_APPLY); 49 | biome.setBiome(x << 2, z << 2, b.getVanillaBiome()); 50 | } 51 | } 52 | } 53 | for(GenerationPopulator g : getGenerationPopulators(world)) { 54 | g.populate(world, chunk, random, chunkX, chunkZ, interp); 55 | } 56 | return chunk; 57 | } 58 | } 59 | 60 | public void attachProfiler(WorldProfiler p) { 61 | this.profiler = p; 62 | } 63 | 64 | public WorldProfiler getProfiler() { 65 | return profiler; 66 | } 67 | 68 | private ProfileFuture measure(String id) { 69 | if(profiler != null) return profiler.measure(id); 70 | return null; 71 | } 72 | 73 | public abstract ChunkData generateBase(@NotNull World world, @NotNull Random random, int x, int z, ChunkInterpolator noise); 74 | 75 | public abstract int getNoiseOctaves(World w); 76 | 77 | public abstract double getNoiseFrequency(World w); 78 | 79 | public abstract List getGenerationPopulators(World w); 80 | 81 | public abstract org.polydev.gaea.biome.BiomeGrid getBiomeGrid(World w); 82 | 83 | public FastNoiseLite getNoiseGenerator() { 84 | return gen; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/generation/GenerationPhase.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.generation; 2 | 3 | /** 4 | * The phase of terrain generation. Used for modifying values based on the phase of generation. 5 | */ 6 | public enum GenerationPhase { 7 | BASE, POPULATE, GENERATION_POPULATE, PALETTE_APPLY, POST_GEN; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/generation/GenerationPopulator.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.generation; 2 | 3 | import org.bukkit.World; 4 | import org.bukkit.generator.ChunkGenerator; 5 | import org.polydev.gaea.math.ChunkInterpolator; 6 | 7 | import java.util.Random; 8 | 9 | public abstract class GenerationPopulator { 10 | public abstract void populate(World world, ChunkGenerator.ChunkData chunk, Random r, int chunkX, int chunkZ, ChunkInterpolator interp); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/lang/Language.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.lang; 2 | 3 | import org.bukkit.command.CommandSender; 4 | import org.bukkit.configuration.InvalidConfigurationException; 5 | import org.bukkit.configuration.file.YamlConfiguration; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.util.List; 11 | import java.util.logging.Level; 12 | import java.util.logging.Logger; 13 | 14 | public class Language extends YamlConfiguration { 15 | public Language(File file) throws IOException, InvalidConfigurationException { 16 | load(file); 17 | } 18 | @Override 19 | public void load(@NotNull File file) throws IOException, InvalidConfigurationException { 20 | super.load(file); 21 | } 22 | @SuppressWarnings("unchecked") 23 | public Message getMessage(String id) { 24 | Object m = get(id); 25 | Message temp; 26 | if(m instanceof List) { 27 | temp = new MultiLineMessage((List) m); 28 | } else if(m instanceof String) { 29 | temp = new SingleLineMessage((String) m); 30 | } else return new SingleLineMessage("message:" + id + ":translation_undefined"); 31 | if(temp.isEmpty()) return new SingleLineMessage("message:" + id + ":translation_undefined"); 32 | return temp; 33 | } 34 | public void log(String messageID, Level level, Logger logger, String... args) { 35 | getMessage(messageID).log(logger, level, args); 36 | } 37 | public void send(String messageID, CommandSender sender, String... args) { 38 | getMessage(messageID).send(sender, args); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/lang/Message.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.lang; 2 | 3 | import org.bukkit.command.CommandSender; 4 | 5 | import java.util.logging.Level; 6 | import java.util.logging.Logger; 7 | 8 | public interface Message { 9 | void log(Logger logger, Level level, String... args); 10 | void send(CommandSender sender, String... args); 11 | boolean isEmpty(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/lang/MultiLineMessage.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.lang; 2 | 3 | import org.bukkit.ChatColor; 4 | import org.bukkit.command.CommandSender; 5 | 6 | import java.util.Arrays; 7 | import java.util.List; 8 | import java.util.logging.Level; 9 | import java.util.logging.Logger; 10 | 11 | public class MultiLineMessage implements Message { 12 | private final List message; 13 | public MultiLineMessage(List message) { 14 | this.message = message; 15 | } 16 | @Override 17 | public void log(Logger logger, Level level, String... args) { 18 | for(String line: message) { 19 | logger.log(level, ChatColor.translateAlternateColorCodes('&', String.format(line, Arrays.asList(args).toArray()))); 20 | } 21 | } 22 | 23 | @Override 24 | public void send(CommandSender sender, String... args) { 25 | for(String line: message) { 26 | sender.sendMessage(ChatColor.translateAlternateColorCodes('&', String.format(line, Arrays.asList(args).toArray()))); 27 | } 28 | } 29 | 30 | @Override 31 | public boolean isEmpty() { 32 | return message == null || message.isEmpty(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/lang/SingleLineMessage.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.lang; 2 | 3 | import org.bukkit.ChatColor; 4 | import org.bukkit.command.CommandSender; 5 | 6 | import java.util.Arrays; 7 | import java.util.logging.Level; 8 | import java.util.logging.Logger; 9 | 10 | public class SingleLineMessage implements Message { 11 | private final String message; 12 | public SingleLineMessage(String message) { 13 | this.message = message; 14 | } 15 | @Override 16 | public void log(Logger logger, Level level, String... args) { 17 | logger.log(level, ChatColor.translateAlternateColorCodes('&', String.format(message, Arrays.asList(args).toArray()))); 18 | } 19 | 20 | @Override 21 | public void send(CommandSender sender, String... args) { 22 | sender.sendMessage(ChatColor.translateAlternateColorCodes('&', String.format(message, Arrays.asList(args).toArray()))); 23 | } 24 | 25 | @Override 26 | public boolean isEmpty() { 27 | return message == null || message.equals(""); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/math/ChunkInterpolator.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.math; 2 | 3 | import org.bukkit.World; 4 | import org.polydev.gaea.biome.BiomeGrid; 5 | 6 | public interface ChunkInterpolator { 7 | double getNoise(double x, double z); 8 | 9 | double getNoise(double x, double y, double z); 10 | 11 | enum InterpolationType { 12 | /** 13 | * 2D (Bilinear) interpolation 14 | */ 15 | BILINEAR { 16 | @Override 17 | public ChunkInterpolator getInstance(World w, int chunkX, int chunkZ, BiomeGrid grid, FastNoiseLite noise) { 18 | return new ChunkInterpolator2(w, chunkX, chunkZ, grid, noise); 19 | } 20 | }, 21 | /** 22 | * 3D (Trilinear) interpolation 23 | */ 24 | TRILINEAR { 25 | @Override 26 | public ChunkInterpolator getInstance(World w, int chunkX, int chunkZ, BiomeGrid grid, FastNoiseLite noise) { 27 | return new ChunkInterpolator3(w, chunkX, chunkZ, grid, noise); 28 | } 29 | }; 30 | public abstract ChunkInterpolator getInstance(World w, int chunkX, int chunkZ, BiomeGrid grid, FastNoiseLite noise); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/math/ChunkInterpolator2.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.math; 2 | 3 | import org.bukkit.World; 4 | import org.polydev.gaea.biome.BiomeGrid; 5 | import org.polydev.gaea.biome.Generator; 6 | import org.polydev.gaea.generation.GenerationPhase; 7 | 8 | /** 9 | * Class to abstract away the 16 Interpolators needed to generate a chunk.
10 | * Contains method to get interpolated noise at a coordinate within the chunk. 11 | */ 12 | public class ChunkInterpolator2 implements ChunkInterpolator { 13 | private final Interpolator[][] interpGrid = new Interpolator[4][4]; 14 | 15 | private final int xOrigin; 16 | private final int zOrigin; 17 | private final FastNoiseLite noise; 18 | private final World w; 19 | 20 | /** 21 | * Instantiates a ChunkInterpolator at a pair of chunk coordinates, with a BiomeGrid and FastNoiseLite instance. 22 | * 23 | * @param chunkX X coordinate of the chunk. 24 | * @param chunkZ Z coordinate of the chunk. 25 | * @param grid BiomeGrid to use for noise fetching. 26 | * @param noise FastNoiseLite instance to use. 27 | */ 28 | public ChunkInterpolator2(World w, int chunkX, int chunkZ, BiomeGrid grid, FastNoiseLite noise) { 29 | this.xOrigin = chunkX << 4; 30 | this.zOrigin = chunkZ << 4; 31 | this.noise = noise; 32 | this.w = w; 33 | Generator[][] gridTemp = new Generator[8][8]; 34 | for(int x = - 2; x < 6; x++) { 35 | for(int z = - 2; z < 6; z++) { 36 | gridTemp[x + 2][z + 2] = grid.getBiome(xOrigin + (x << 2), zOrigin + (z << 2), GenerationPhase.BASE).getGenerator(); 37 | } 38 | } 39 | for(byte x = 0; x < 4; x++) { 40 | for(byte z = 0; z < 4; z++) { 41 | interpGrid[x][z] = new Interpolator(biomeAvg(x, z, gridTemp), 42 | biomeAvg(x + 1, z, gridTemp), 43 | biomeAvg(x, z + 1, gridTemp), 44 | biomeAvg(x + 1, z + 1, gridTemp), gridTemp[x+1][z+1].getInterpolationType()); 45 | } 46 | } 47 | } 48 | 49 | private double biomeAvg(int x, int z, Generator[][] g) { 50 | return (g[x + 3][z + 2].getNoise(noise, w, (x << 2) + xOrigin, (z << 2) + zOrigin) 51 | + g[x + 1][z + 2].getNoise(noise, w, (x << 2) + xOrigin, (z << 2) + zOrigin) 52 | + g[x + 2][z + 3].getNoise(noise, w, (x << 2) + xOrigin, (z << 2) + zOrigin) 53 | + g[x + 2][z + 1].getNoise(noise, w, (x << 2) + xOrigin, (z << 2) + zOrigin)) / 4D; 54 | } 55 | 56 | /** 57 | * Gets the noise at a pair of internal chunk coordinates. 58 | * 59 | * @param x The internal X coordinate (0-15). 60 | * @param z The internal Z coordinate (0-15). 61 | * @return double - The interpolated noise at the coordinates. 62 | */ 63 | @Override 64 | public double getNoise(double x, double z) { 65 | return interpGrid[((int) x) / 4][((int) z) / 4].bilerp((x % 4) / 4, (z % 4) / 4); 66 | } 67 | 68 | @Override 69 | public double getNoise(double x, double y, double z) { 70 | return getNoise(x, z); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/math/ChunkInterpolator3.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.math; 2 | 3 | import net.jafama.FastMath; 4 | import org.bukkit.World; 5 | import org.polydev.gaea.biome.BiomeGrid; 6 | import org.polydev.gaea.biome.Generator; 7 | import org.polydev.gaea.generation.GenerationPhase; 8 | 9 | /** 10 | * Class to abstract away the 16 Interpolators needed to generate a chunk.
11 | * Contains method to get interpolated noise at a coordinate within the chunk. 12 | */ 13 | public class ChunkInterpolator3 implements ChunkInterpolator { 14 | private final Interpolator3[][][] interpGrid = new Interpolator3[4][64][4]; 15 | private final Generator[][] gens = new Generator[7][7]; 16 | private final boolean[][] needsBiomeInterp = new boolean[5][5]; 17 | private final double[][][] noiseStorage = new double[7][7][65]; 18 | private final FastNoiseLite noise; 19 | private final int xOrigin; 20 | private final int zOrigin; 21 | private final World w; 22 | 23 | /** 24 | * Instantiates a 3D ChunkInterpolator at a pair of chunk coordinates, with a BiomeGrid and FastNoiseLite instance. 25 | * 26 | * @param chunkX X coordinate of the chunk. 27 | * @param chunkZ Z coordinate of the chunk. 28 | * @param grid BiomeGrid to use for noise fetching. 29 | * @param noise FastNoiseLite instance to use. 30 | */ 31 | public ChunkInterpolator3(World w, int chunkX, int chunkZ, BiomeGrid grid, FastNoiseLite noise) { 32 | this.xOrigin = chunkX << 4; 33 | this.zOrigin = chunkZ << 4; 34 | this.noise = noise; 35 | this.w = w; 36 | 37 | 38 | for(int x = - 1; x < 6; x++) { 39 | for(int z = - 1; z < 6; z++) { 40 | gens[x + 1][z + 1] = grid.getBiome(xOrigin + (x << 2), zOrigin + (z << 2), GenerationPhase.BASE).getGenerator(); 41 | } 42 | } 43 | for(int x = 0; x < 5; x++) { 44 | for(int z = 0; z < 5; z++) { 45 | needsBiomeInterp[x][z] = compareGens(x+1, z+1); 46 | } 47 | } 48 | 49 | storeNoise(); 50 | 51 | for(byte x = 0; x < 4; x++) { 52 | for(byte z = 0; z < 4; z++) { 53 | for(int y = 0; y < 64; y++) { 54 | interpGrid[x][y][z] = new Interpolator3( 55 | biomeAvg(x, y, z), 56 | biomeAvg(x + 1, y, z), 57 | biomeAvg(x, y + 1, z), 58 | biomeAvg(x + 1, y + 1, z), 59 | biomeAvg(x, y, z + 1), 60 | biomeAvg(x + 1, y, z + 1), 61 | biomeAvg(x, y + 1, z + 1), 62 | biomeAvg(x + 1, y + 1, z + 1), gens[x+1][z+1].getInterpolationType()); 63 | } 64 | } 65 | } 66 | } 67 | 68 | private boolean compareGens(int x, int z) { 69 | Generator comp = gens[x][z]; 70 | if(!comp.equals(gens[x+1][z])) return true; 71 | 72 | if(!comp.equals(gens[x][z+1])) return true; 73 | 74 | if(!comp.equals(gens[x-1][z])) return true; 75 | 76 | if(!comp.equals(gens[x][z-1])) return true; 77 | 78 | if(!comp.equals(gens[x+1][z+1])) return true; 79 | 80 | if(!comp.equals(gens[x-1][z-1])) return true; 81 | 82 | if(!comp.equals(gens[x+1][z-1])) return true; 83 | 84 | return !comp.equals(gens[x - 1][z + 1]); 85 | } 86 | private void storeNoise() { 87 | for(byte x = - 1; x < 6; x++) { 88 | for(byte z = - 1; z < 6; z++) { 89 | for(int y = 0; y < 64; y++) { 90 | noiseStorage[x + 1][z + 1][y] = gens[x + 1][z + 1].getNoise(noise, w, (x << 2) + xOrigin, y << 2, (z << 2) + zOrigin); 91 | } 92 | } 93 | } 94 | } 95 | 96 | private double biomeAvg(int x, int y, int z) { 97 | if(needsBiomeInterp[x][z]) return (noiseStorage[x + 2][z + 1][y] 98 | + noiseStorage[x][z + 1][y] 99 | + noiseStorage[x + 1][z + 2][y] 100 | + noiseStorage[x + 1][z][y] 101 | + noiseStorage[x][z][y] 102 | + noiseStorage[x + 2][z + 2][y] 103 | + noiseStorage[x + 2][z][y] 104 | + noiseStorage[x][z + 2][y] 105 | + noiseStorage[x + 1][z + 1][y] 106 | ) / 9D; 107 | else { 108 | if(gens[x+1][z+1].useMinimalInterpolation()) return noiseStorage[x+1][z+1][y]; 109 | else return (noiseStorage[x + 2][z + 1][y] 110 | + noiseStorage[x][z + 1][y] 111 | + noiseStorage[x + 1][z + 2][y] 112 | + noiseStorage[x + 1][z][y] 113 | + noiseStorage[x+1][z+1][y]) / 5D; 114 | } 115 | } 116 | 117 | @Override 118 | public double getNoise(double x, double z) { 119 | return getNoise(x, 0, z); 120 | } 121 | 122 | /** 123 | * Gets the noise at a pair of internal chunk coordinates. 124 | * 125 | * @param x The internal X coordinate (0-15). 126 | * @param z The internal Z coordinate (0-15). 127 | * @return double - The interpolated noise at the coordinates. 128 | */ 129 | @Override 130 | public double getNoise(double x, double y, double z) { 131 | return interpGrid[reRange(((int) x) / 4, 3)][reRange(((int) y) / 4, 63)][reRange(((int) z) / 4, 3)].trilerp((x % 4) / 4, (y % 4) / 4, (z % 4) / 4); 132 | } 133 | 134 | private static int reRange(int value, int high) { 135 | return FastMath.max(FastMath.min(value, high), 0); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/math/Interpolator.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.math; 2 | 3 | import net.jafama.FastMath; 4 | 5 | /** 6 | * Class for bilinear interpolation of values arranged on a unit square. 7 | */ 8 | public class Interpolator { 9 | private final double v0, v1, v2, v3; 10 | private final Type type; 11 | 12 | /** 13 | * Constructs an interpolator with given values as vertices of a unit square. 14 | * 15 | * @param v0 - (0,0) 16 | * @param v1 - (1,0) 17 | * @param v2 - (0,1) 18 | * @param v3 - (1,1) 19 | */ 20 | public Interpolator(double v0, double v1, double v2, double v3, Type type) { 21 | this.v0 = v0; 22 | this.v1 = v1; 23 | this.v2 = v2; 24 | this.v3 = v3; 25 | this.type = type; 26 | } 27 | 28 | /** 29 | * 1D Linear interpolation between 2 points 1 unit apart. 30 | * 31 | * @param t - Distance from v0. Total distance between v0 and v1 is 1 unit. 32 | * @param v0 - Value at v0. 33 | * @param v1 - Value at v1. 34 | * @return double - The interpolated value. 35 | */ 36 | public static double lerp(double t, double v0, double v1, Type type) { 37 | switch(type) { 38 | case LINEAR: return v0 + t * (v1 - v0); 39 | case NEAREST_NEIGHBOR: return FastMath.abs(v0-t) > FastMath.abs(v1-t) ? v1 : v0; 40 | default: throw new IllegalStateException(); 41 | } 42 | } 43 | 44 | /** 45 | * 2D Bilinear interpolation between 4 points on a unit square. 46 | * 47 | * @param s - X value 48 | * @param t - Z value 49 | * @return double - The interpolated value. 50 | */ 51 | public double bilerp(double s, double t) { 52 | double v01 = lerp(s, v0, v1, type); 53 | double v23 = lerp(s, v2, v3, type); 54 | double v = lerp(t, v01, v23, type); 55 | return v; 56 | } 57 | 58 | public enum Type { 59 | LINEAR, NEAREST_NEIGHBOR 60 | } 61 | } -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/math/Interpolator3.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.math; 2 | 3 | /** 4 | * Class for bilinear interpolation of values arranged on a unit square. 5 | */ 6 | public class Interpolator3 { 7 | private final double _000, _100, _010, _110, _001, _101, _011, _111; 8 | private final Interpolator.Type type; 9 | 10 | /** 11 | * Constructs an interpolator with given values as vertices of a unit cube. 12 | * * @param _000 The value at (t, u, v) = (0, 0, 0). 13 | * * @param _100 The value at (t, u, v) = (1, 0, 0). 14 | * * @param _010 The value at (t, u, v) = (0, 1, 0). 15 | * * @param _110 The value at (t, u, v) = (1, 1, 0). 16 | * * @param _001 The value at (t, u, v) = (0, 0, 1). 17 | * * @param _101 The value at (t, u, v) = (1, 0, 1). 18 | * * @param _011 The value at (t, u, v) = (0, 1, 1). 19 | * * @param _111 The value at (t, u, v) = (1, 1, 1). 20 | */ 21 | public Interpolator3(double _000, double _100, 22 | double _010, double _110, double _001, double _101, 23 | double _011, double _111, Interpolator.Type type) { 24 | this._000 = _000; 25 | this._001 = _001; 26 | this._010 = _010; 27 | this._011 = _011; 28 | this._100 = _100; 29 | this._101 = _101; 30 | this._110 = _110; 31 | this._111 = _111; 32 | this.type = type; 33 | } 34 | 35 | public double trilerp(double x, double y, double z) { 36 | Interpolator top = new Interpolator(_000, _010, _001, _011, type); 37 | Interpolator bottom = new Interpolator(_100, _110, _101, _111, type); 38 | return Interpolator.lerp(x, top.bilerp(y, z), bottom.bilerp(y, z), type); 39 | } 40 | } -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/math/MathUtil.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.math; 2 | 3 | import org.polydev.gaea.util.FastRandom; 4 | import net.jafama.FastMath; 5 | 6 | import java.util.Random; 7 | 8 | /** 9 | * Utility class for mathematical functions. 10 | */ 11 | public class MathUtil { 12 | /** 13 | * Gets the standard deviation of an array of doubles. 14 | * 15 | * @param numArray The array of numbers to calculate the standard deviation of. 16 | * @return double - The standard deviation. 17 | */ 18 | public static double standardDeviation(double[] numArray) { 19 | double sum = 0.0, standardDeviation = 0.0; 20 | int length = numArray.length; 21 | 22 | for(double num : numArray) { 23 | sum += num; 24 | } 25 | 26 | double mean = sum / length; 27 | 28 | for(double num : numArray) { 29 | standardDeviation += FastMath.pow(num - mean, 2); 30 | } 31 | 32 | return FastMath.sqrt(standardDeviation / length); 33 | } 34 | 35 | /** 36 | * Gets the carver seed for a chunk. 37 | * 38 | * @param chunkX Chunk's X coordinate 39 | * @param chunkZ Chunk's Z coordinate 40 | * @param seed World seed 41 | * @return long - The carver seed. 42 | */ 43 | public static long getCarverChunkSeed(int chunkX, int chunkZ, long seed) { 44 | Random r = new FastRandom(seed); 45 | return chunkX * r.nextLong() ^ chunkZ * r.nextLong() ^ seed; 46 | } 47 | 48 | public static long hashToLong(String s) { 49 | if(s == null) { 50 | return 0; 51 | } 52 | long hash = 0; 53 | for(char c : s.toCharArray()) { 54 | hash = 31L * hash + c; 55 | } 56 | return hash; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/math/ProbabilityCollection.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.math; 2 | 3 | import org.polydev.gaea.biome.NormalizationUtil; 4 | 5 | import java.util.HashSet; 6 | import java.util.Random; 7 | import java.util.Set; 8 | import java.util.concurrent.ThreadLocalRandom; 9 | 10 | @SuppressWarnings("unchecked") 11 | public class ProbabilityCollection { 12 | private final Set cont = new HashSet<>(); 13 | private Object[] array = new Object[0]; 14 | private int size; 15 | 16 | public ProbabilityCollection add(E item, int probability) { 17 | if(!cont.contains(item)) size++; 18 | cont.add(item); 19 | int oldLength = array.length; 20 | Object[] newArray = new Object[array.length + probability]; 21 | System.arraycopy(array, 0, newArray, 0, array.length); // Expand array. 22 | array = newArray; 23 | for(int i = oldLength; i < array.length; i++) array[i] = item; 24 | return this; 25 | } 26 | 27 | public E get() { 28 | if(array.length == 0) return null; 29 | return (E) array[ThreadLocalRandom.current().nextInt(array.length)]; 30 | } 31 | 32 | public E get(Random r) { 33 | if(array.length == 0) return null; 34 | return (E) array[r.nextInt(array.length)]; 35 | } 36 | 37 | public E get(FastNoiseLite n, double x, double z) { 38 | if(array.length == 0) return null; 39 | return (E) array[NormalizationUtil.normalize(n.getNoise(x, z), array.length, 1)]; 40 | } 41 | 42 | public int getTotalProbability() { 43 | return array.length; 44 | } 45 | 46 | public int size() { 47 | return size; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/math/Range.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.math; 2 | 3 | import net.jafama.FastMath; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.util.Iterator; 7 | import java.util.Random; 8 | 9 | public class Range implements Iterable { 10 | private int min; 11 | private int max; 12 | 13 | public Range(int min, int max) { 14 | if(min > max) throw new IllegalArgumentException("Minimum must not be grater than maximum!"); 15 | this.max = max; 16 | this.min = min; 17 | } 18 | 19 | public boolean isInRange(int test) { 20 | return test >= min && test < max; 21 | } 22 | 23 | public int getMax() { 24 | return max; 25 | } 26 | 27 | public Range setMax(int max) { 28 | this.max = max; 29 | return this; 30 | } 31 | 32 | public int getMin() { 33 | return min; 34 | } 35 | 36 | public Range setMin(int min) { 37 | this.min = min; 38 | return this; 39 | } 40 | 41 | public int getRange() { 42 | return max - min; 43 | } 44 | 45 | public Range multiply(int mult) { 46 | min *= mult; 47 | max *= mult; 48 | return this; 49 | } 50 | 51 | public Range reflect(int pt) { 52 | return new Range(2 * pt - this.getMax(), 2 * pt - this.getMin()); 53 | } 54 | 55 | public int get(Random r) { 56 | return r.nextInt((max - min) + 1) + min; 57 | } 58 | 59 | public Range intersects(Range other) { 60 | try { 61 | return new Range(FastMath.max(this.getMin(), other.getMin()), FastMath.min(this.getMax(), other.getMax())); 62 | } catch(IllegalArgumentException e) { 63 | return null; 64 | } 65 | } 66 | 67 | public Range add(int add) { 68 | this.min += add; 69 | this.max += add; 70 | return this; 71 | } 72 | 73 | public Range sub(int sub) { 74 | this.min -= sub; 75 | this.max -= sub; 76 | return this; 77 | } 78 | 79 | @Override 80 | public String toString() { 81 | return "Min: " + getMin() + ", Max:" + getMax(); 82 | } 83 | 84 | @Override 85 | public int hashCode() { 86 | return min * 31 + max; 87 | } 88 | 89 | @Override 90 | public boolean equals(Object obj) { 91 | if(! (obj instanceof Range)) return false; 92 | Range other = (Range) obj; 93 | return other.getMin() == this.getMin() && other.getMax() == this.getMax(); 94 | } 95 | 96 | @NotNull 97 | @Override 98 | public Iterator iterator() { 99 | return new RangeIterator(this); 100 | } 101 | 102 | private static class RangeIterator implements Iterator { 103 | private final Range m; 104 | private Integer current; 105 | 106 | public RangeIterator(Range m) { 107 | this.m = m; 108 | current = m.getMin(); 109 | } 110 | 111 | @Override 112 | public boolean hasNext() { 113 | return current < m.getMax(); 114 | } 115 | 116 | @Override 117 | public Integer next() { 118 | current++; 119 | return current - 1; 120 | } 121 | } 122 | } 123 | 124 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/population/ChunkCoordinate.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.population; 2 | 3 | import org.bukkit.Chunk; 4 | 5 | import java.io.Serializable; 6 | import java.util.UUID; 7 | 8 | public class ChunkCoordinate implements Serializable { 9 | public static final long serialVersionUID = 7102462856296750285L; 10 | private final int x; 11 | private final int z; 12 | private final UUID worldID; 13 | 14 | public ChunkCoordinate(int x, int z, UUID worldID) { 15 | this.x = x; 16 | this.z = z; 17 | this.worldID = worldID; 18 | } 19 | 20 | public ChunkCoordinate(Chunk c) { 21 | this.x = c.getX(); 22 | this.z = c.getZ(); 23 | this.worldID = c.getWorld().getUID(); 24 | } 25 | 26 | public UUID getWorldID() { 27 | return worldID; 28 | } 29 | 30 | public int getX() { 31 | return x; 32 | } 33 | 34 | public int getZ() { 35 | return z; 36 | } 37 | 38 | @Override 39 | public int hashCode() { 40 | return x * 31 + z; 41 | } 42 | 43 | @Override 44 | public boolean equals(Object obj) { 45 | if(! (obj instanceof ChunkCoordinate)) return false; 46 | ChunkCoordinate other = (ChunkCoordinate) obj; 47 | return other.getX() == x && other.getZ() == z; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/population/GaeaBlockPopulator.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.population; 2 | 3 | import org.bukkit.Chunk; 4 | import org.bukkit.World; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import java.util.Random; 8 | 9 | public abstract class GaeaBlockPopulator { 10 | public abstract void populate(@NotNull World world, @NotNull Random random, @NotNull Chunk chunk); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/population/PopulationManager.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.population; 2 | 3 | import org.bukkit.Chunk; 4 | import org.bukkit.World; 5 | import org.bukkit.generator.BlockPopulator; 6 | import org.bukkit.plugin.java.JavaPlugin; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.polydev.gaea.Gaea; 9 | import org.polydev.gaea.profiler.ProfileFuture; 10 | import org.polydev.gaea.profiler.WorldProfiler; 11 | import org.polydev.gaea.util.FastRandom; 12 | import org.polydev.gaea.util.GlueList; 13 | import org.polydev.gaea.util.SerializationUtil; 14 | 15 | import java.io.File; 16 | import java.io.IOException; 17 | import java.util.HashSet; 18 | import java.util.List; 19 | import java.util.Random; 20 | 21 | public class PopulationManager extends BlockPopulator { 22 | private final List attachedPopulators = new GlueList<>(); 23 | private final HashSet needsPop = new HashSet<>(); 24 | private final JavaPlugin main; 25 | private final Object popLock = new Object(); 26 | private WorldProfiler profiler; 27 | 28 | public PopulationManager(JavaPlugin main) { 29 | this.main = main; 30 | } 31 | 32 | public void attach(GaeaBlockPopulator populator) { 33 | this.attachedPopulators.add(populator); 34 | } 35 | 36 | @Override 37 | public void populate(@NotNull World world, @NotNull Random random, @NotNull Chunk chunk) { 38 | try(ProfileFuture ignored = measure()) { 39 | needsPop.add(new ChunkCoordinate(chunk)); 40 | int x = chunk.getX(); 41 | int z = chunk.getZ(); 42 | if(main.isEnabled()) { 43 | for(int xi = - 1; xi <= 1; xi++) { 44 | for(int zi = - 1; zi <= 1; zi++) { 45 | if(xi == 0 && zi == 0) continue; 46 | if(world.isChunkGenerated(xi + x, zi + z)) checkNeighbors(xi + x, zi + z, world); 47 | } 48 | } 49 | } 50 | } 51 | } 52 | 53 | private ProfileFuture measure() { 54 | if(profiler != null) return profiler.measure("PopulationManagerTime"); 55 | return null; 56 | } 57 | 58 | public void attachProfiler(WorldProfiler p) { 59 | this.profiler = p; 60 | } 61 | 62 | @SuppressWarnings("unchecked") 63 | public synchronized void saveBlocks(World w) throws IOException { 64 | File f = new File(Gaea.getGaeaFolder(w), "chunks.bin"); 65 | f.createNewFile(); 66 | SerializationUtil.toFile((HashSet) needsPop.clone(), f); 67 | } 68 | 69 | @SuppressWarnings("unchecked") 70 | public synchronized void loadBlocks(World w) throws IOException, ClassNotFoundException { 71 | File f = new File(Gaea.getGaeaFolder(w), "chunks.bin"); 72 | needsPop.addAll((HashSet) SerializationUtil.fromFile(f)); 73 | } 74 | 75 | 76 | // Synchronize to prevent chunks from being queued for population multiple times. 77 | public synchronized void checkNeighbors(int x, int z, World w) { 78 | ChunkCoordinate c = new ChunkCoordinate(x, z, w.getUID()); 79 | if(w.isChunkGenerated(x + 1, z) 80 | && w.isChunkGenerated(x - 1, z) 81 | && w.isChunkGenerated(x, z + 1) 82 | && w.isChunkGenerated(x, z - 1) && needsPop.contains(c)) { 83 | Random random = new FastRandom(w.getSeed()); 84 | long xRand = (random.nextLong() / 2L << 1L) + 1L; 85 | long zRand = (random.nextLong() / 2L << 1L) + 1L; 86 | random.setSeed((long) x * xRand + (long) z * zRand ^ w.getSeed()); 87 | Chunk currentChunk = w.getChunkAt(x, z); 88 | for(GaeaBlockPopulator r : attachedPopulators) { 89 | r.populate(w, random, currentChunk); 90 | } 91 | needsPop.remove(c); 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/profiler/DataHolder.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.profiler; 2 | 3 | import net.md_5.bungee.api.ChatColor; 4 | import net.jafama.FastMath; 5 | 6 | /** 7 | * Class to hold a profiler data value. Contains formatting method to highlight value based on desired range. 8 | */ 9 | public class DataHolder { 10 | private final long desired; 11 | private final DataType type; 12 | private final double desiredRangePercent; 13 | 14 | /** 15 | * Constructs a DataHolder with a DataType and a desired value, including a percentage around the desired value considered acceptable 16 | * 17 | * @param type The type of data held in this instance. 18 | * @param desired The desired value. This should be the average value of whatever is being measured. 19 | * @param desiredRangePercent The percentage around the desired value to be considered acceptable. 20 | */ 21 | public DataHolder(DataType type, long desired, double desiredRangePercent) { 22 | this.desired = desired; 23 | this.type = type; 24 | this.desiredRangePercent = desiredRangePercent; 25 | } 26 | 27 | /** 28 | * Returns a String, formatted with Bungee ChatColors.
29 | * GREEN if the value is better than desired and outside of acceptable range.
30 | * YELLOW if the value is better or worse than desired, and within acceptable range.
31 | * RED if the value is worse than desired and outside of acceptable range.
32 | * 33 | * @param data The data to format. 34 | * @return String - The formatted data. 35 | */ 36 | public String getFormattedData(long data) { 37 | double range = desiredRangePercent * desired; 38 | ChatColor color = ChatColor.YELLOW; 39 | if(FastMath.abs(data - desired) > range) { 40 | if(data > desired) color = type.getDesire().getHighColor(); 41 | else color = type.getDesire().getLowColor(); 42 | } 43 | return color + type.getFormatted(data) + ChatColor.RESET; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/profiler/DataType.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.profiler; 2 | 3 | import net.jafama.FastMath; 4 | 5 | public enum DataType { 6 | PERIOD_MILLISECONDS(Desire.LOW, 1000000, "ms"), PERIOD_NANOSECONDS(Desire.LOW, 1, "ns"); 7 | private final Desire desire; 8 | private final long divisor; 9 | private final String unit; 10 | 11 | DataType(Desire d, long divisor, String unit) { 12 | this.desire = d; 13 | this.divisor = divisor; 14 | this.unit = unit; 15 | } 16 | 17 | public String getFormatted(long value) { 18 | return (double) FastMath.round(((double) value / divisor) * 100D) / 100D + unit; 19 | } 20 | 21 | public Desire getDesire() { 22 | return desire; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/profiler/Desire.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.profiler; 2 | 3 | import net.md_5.bungee.api.ChatColor; 4 | 5 | /** 6 | * Enum to represent the "goal" of a value, whether it is desirable for the value to be high (e.g. Frequency), or low (e.g. Period) 7 | */ 8 | public enum Desire { 9 | LOW(ChatColor.RED, ChatColor.GREEN), HIGH(ChatColor.GREEN, ChatColor.RED); 10 | 11 | private final ChatColor high; 12 | private final ChatColor low; 13 | 14 | Desire(ChatColor high, ChatColor low) { 15 | this.high = high; 16 | this.low = low; 17 | } 18 | 19 | /** 20 | * Gets the color to display when the numerical value is higher than desired. 21 | * 22 | * @return ChatColor - color of the value. 23 | */ 24 | public ChatColor getHighColor() { 25 | return high; 26 | } 27 | 28 | /** 29 | * Gets the color to display when the numerical value is lower than desired. 30 | * 31 | * @return ChatColor - color of the value. 32 | */ 33 | public ChatColor getLowColor() { 34 | return low; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/profiler/Measurement.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.profiler; 2 | 3 | import net.jafama.FastMath; 4 | import org.bukkit.Bukkit; 5 | import org.polydev.gaea.math.MathUtil; 6 | import org.polydev.gaea.util.GlueList; 7 | 8 | import java.math.BigInteger; 9 | import java.util.List; 10 | 11 | /** 12 | * Class to record and hold all data for a single type of measurement performed by the profiler. 13 | */ 14 | public class Measurement { 15 | private final List measurements; 16 | private final long desirable; 17 | private final DataType type; 18 | private long min = Long.MAX_VALUE; 19 | private long max = Long.MIN_VALUE; 20 | 21 | /** 22 | * Constructs a new Measurement with a desired value and DataType. 23 | * 24 | * @param desirable The desired value of the measurement. 25 | * @param type The type of data the measurement is holding. 26 | */ 27 | public Measurement(long desirable, DataType type) { 28 | this.desirable = desirable; 29 | this.type = type; 30 | measurements = new GlueList<>(); 31 | } 32 | 33 | public void record(long value) { 34 | max = FastMath.max(value, max); 35 | min = FastMath.min(value, min); 36 | if(value / 1000000 > 5000) Bukkit.getLogger().warning("Measurement took " + type.getFormatted(value)); 37 | measurements.add(value); 38 | } 39 | 40 | public int size() { 41 | return measurements.size(); 42 | } 43 | 44 | public ProfileFuture beginMeasurement() { 45 | ProfileFuture future = new ProfileFuture(); 46 | long current = System.nanoTime(); 47 | future.thenRun(() -> record(System.nanoTime() - current)); 48 | return future; 49 | } 50 | 51 | public void reset() { 52 | min = Long.MAX_VALUE; 53 | max = Long.MIN_VALUE; 54 | measurements.clear(); 55 | } 56 | 57 | public DataHolder getDataHolder() { 58 | return new DataHolder(type, desirable, 0.25); 59 | } 60 | 61 | public long getMin() { 62 | if(min == Long.MAX_VALUE) return 0; 63 | return min; 64 | } 65 | 66 | public long getMax() { 67 | if(max == Long.MIN_VALUE) return 0; 68 | return max; 69 | } 70 | 71 | public long average() { 72 | BigInteger running = new BigInteger("0"); 73 | List mTemp = new GlueList<>(measurements); 74 | for(Long l : mTemp) { 75 | running = running.add(BigInteger.valueOf(l)); 76 | } 77 | if(measurements.size() == 0) return 0; 78 | return running.divide(BigInteger.valueOf(measurements.size())).longValue(); 79 | } 80 | 81 | public double getStdDev() { 82 | List mTemp = new GlueList<>(measurements); 83 | double[] vals = new double[mTemp.size()]; 84 | for(int i = 0; i < mTemp.size(); i++) { 85 | vals[i] = mTemp.get(i); 86 | } 87 | return MathUtil.standardDeviation(vals); 88 | } 89 | 90 | public int entries() { 91 | return measurements.size(); 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/profiler/ProfileFuture.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.profiler; 2 | 3 | import java.util.concurrent.CompletableFuture; 4 | 5 | public class ProfileFuture extends CompletableFuture implements AutoCloseable { 6 | public ProfileFuture() { 7 | super(); 8 | } 9 | 10 | public boolean complete() { 11 | return super.complete(true); 12 | } 13 | 14 | @Override 15 | public void close() { 16 | this.complete(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/profiler/WorldProfiler.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.profiler; 2 | 3 | import com.google.common.collect.BiMap; 4 | import com.google.common.collect.HashBiMap; 5 | import net.jafama.FastMath; 6 | import org.bukkit.ChatColor; 7 | import org.bukkit.World; 8 | import org.polydev.gaea.generation.GaeaChunkGenerator; 9 | 10 | import java.util.Map; 11 | 12 | public class WorldProfiler { 13 | private final BiMap measures = HashBiMap.create(); 14 | private final World world; 15 | private boolean isProfiling; 16 | 17 | public WorldProfiler(World w) { 18 | if(! (w.getGenerator() instanceof GaeaChunkGenerator)) 19 | throw new IllegalArgumentException("Attempted to instantiate profiler on non-Gaea managed world!"); 20 | this.addMeasurement(new Measurement(2500000, DataType.PERIOD_MILLISECONDS), "TotalChunkGenTime") 21 | .addMeasurement(new Measurement(2500000, DataType.PERIOD_MILLISECONDS), "ChunkBaseGenTime") 22 | .addMeasurement(new Measurement(2000000, DataType.PERIOD_MILLISECONDS), "BiomeApplyTime") 23 | .addMeasurement(new Measurement(2000000, DataType.PERIOD_MILLISECONDS), "PopulationManagerTime"); 24 | isProfiling = false; 25 | this.world = w; 26 | ((GaeaChunkGenerator) w.getGenerator()).attachProfiler(this); 27 | } 28 | 29 | public String getResultsFormatted() { 30 | if(! isProfiling) return "Profiler is not currently running."; 31 | StringBuilder result = new StringBuilder(ChatColor.GOLD + "Gaea World Profiler Results (Min / Avg / Max / Std Dev): \n"); 32 | for(Map.Entry e : measures.entrySet()) { 33 | result.append(ChatColor.GOLD) 34 | .append(e.getKey()) 35 | .append(": ") 36 | .append(e.getValue().getDataHolder().getFormattedData(e.getValue().getMin())) 37 | .append(ChatColor.GOLD) 38 | .append(" / ") 39 | .append(e.getValue().getDataHolder().getFormattedData(e.getValue().average())) 40 | .append(ChatColor.GOLD) 41 | .append(" / ") 42 | .append(e.getValue().getDataHolder().getFormattedData(e.getValue().getMax())) 43 | .append(ChatColor.GOLD) 44 | .append(" / ") 45 | .append(ChatColor.GREEN) 46 | .append((double) FastMath.round((e.getValue().getStdDev() / 1000000) * 100D) / 100D) 47 | .append("ms") 48 | .append(ChatColor.GOLD).append(" (x").append(e.getValue().size()).append(")\n"); 49 | } 50 | return result.toString(); 51 | } 52 | 53 | public void reset() { 54 | for(Map.Entry e : measures.entrySet()) { 55 | e.getValue().reset(); 56 | } 57 | } 58 | 59 | public WorldProfiler addMeasurement(Measurement m, String name) { 60 | measures.put(name, m); 61 | return this; 62 | } 63 | 64 | public void setMeasurement(String id, long value) { 65 | if(isProfiling) measures.get(id).record(value); 66 | } 67 | 68 | public ProfileFuture measure(String id) { 69 | if(isProfiling) return measures.get(id).beginMeasurement(); 70 | else return null; 71 | } 72 | 73 | public String getID(Measurement m) { 74 | return measures.inverse().get(m); 75 | } 76 | 77 | public boolean isProfiling() { 78 | return isProfiling; 79 | } 80 | 81 | public void setProfiling(boolean enabled) { 82 | this.isProfiling = enabled; 83 | } 84 | 85 | public World getWorld() { 86 | return world; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/serial/MovedObjectInputStream.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.serial; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.ObjectInputStream; 6 | import java.io.ObjectStreamClass; 7 | import java.lang.reflect.Field; 8 | 9 | public class MovedObjectInputStream extends ObjectInputStream { 10 | private final String oldNameSpace; 11 | private final String newNameSpace; 12 | 13 | public MovedObjectInputStream(InputStream in, String oldNameSpace, String newNameSpace) throws IOException { 14 | super(in); 15 | this.oldNameSpace = oldNameSpace; 16 | this.newNameSpace = newNameSpace; 17 | } 18 | 19 | @Override 20 | protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException { 21 | ObjectStreamClass result = super.readClassDescriptor(); 22 | try { 23 | if (result.getName().contains(oldNameSpace)) { 24 | String newClassName = result.getName().replace(oldNameSpace, newNameSpace); 25 | Class localClass = Class.forName(newClassName); 26 | 27 | Field nameField = ObjectStreamClass.class.getDeclaredField("name"); 28 | nameField.setAccessible(true); 29 | nameField.set(result, newClassName); 30 | 31 | ObjectStreamClass localClassDescriptor = ObjectStreamClass.lookup(localClass); 32 | Field suidField = ObjectStreamClass.class.getDeclaredField("suid"); 33 | suidField.setAccessible(true); 34 | suidField.set(result, localClassDescriptor.getSerialVersionUID()); 35 | } 36 | } catch(Exception e) { 37 | throw new IOException("Exception when trying to replace namespace", e); 38 | } 39 | return result; 40 | } 41 | 42 | @Override 43 | protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { 44 | if (desc.getName().contains(oldNameSpace)) { 45 | String newClassName = desc.getName().replace(oldNameSpace, newNameSpace); 46 | return Class.forName(newClassName); 47 | } 48 | return super.resolveClass(desc); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/structures/NMSStructure.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.structures; 2 | 3 | import com.esotericsoftware.reflectasm.ConstructorAccess; 4 | import com.esotericsoftware.reflectasm.MethodAccess; 5 | import org.bukkit.Bukkit; 6 | import org.bukkit.Location; 7 | import org.polydev.gaea.util.FastRandom; 8 | 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.util.Random; 12 | 13 | /** 14 | * Representation of Vanilla Structure Block structure. 15 | * 16 | * @author dfsek 17 | * @since 2.0.0 18 | */ 19 | @SuppressWarnings({"unchecked", "rawtypes"}) 20 | public class NMSStructure { 21 | private static final String version = Bukkit.getServer().getClass().getPackage().getName().replace(".", ",").split(",")[3]; 22 | public static int pasteMethodIndex; 23 | public static int getNBTListMethodIndex; 24 | private static Class definedStructureClass; 25 | private static MethodAccess definedStructureMethodAccess; 26 | private static ConstructorAccess definedStructureConstructorAccess; 27 | private static int loadStructureMethodIndex; 28 | private static int getStructureAsNBTMethodIndex; 29 | private static MethodAccess nbtStreamToolsAccess; 30 | private static int loadNBTStreamFromInputStreamIndex; 31 | private static ConstructorAccess compoundNBTConstructorAccess; 32 | private static MethodAccess compoundNBTTagMethodAccess; 33 | private static MethodAccess listNBTTagMethodAccess; 34 | private static int getNBTListItemMethodIndex; 35 | private static MethodAccess enumBlockRotationMethodAccess; 36 | private static int enumBlockRotationValueOfIndex; 37 | private static MethodAccess craftWorldMethodAccess; 38 | private static int getCraftWorldHandleIndex; 39 | private static MethodAccess definedStructureInfoMethodAccess; 40 | private static int setRotationMethodIndex; 41 | private static ConstructorAccess definedStructureInfoConstructorAccess; 42 | private static int mysteryBooleanMethodIndex; 43 | private static int chunkCoordIntPairMethodIndex; 44 | private static int mysteryBooleancMethodIndex; 45 | private static int setRandomMethodIndex; 46 | private static MethodAccess craftBlockMethodAccess; 47 | private static int craftBlockGetPositionIndex; 48 | 49 | static { 50 | try { 51 | long start = System.nanoTime(); 52 | Bukkit.getLogger().info("[Gaea] Beginning reflections for net.minecraft.server." + version + "."); 53 | Class craftWorldClass = Class.forName("org.bukkit.craftbukkit." + version + ".CraftWorld"); 54 | Class compoundNBTTagClass = Class.forName("net.minecraft.server." + version + ".NBTTagCompound"); 55 | Class definedStructureInfoClass = Class.forName("net.minecraft.server." + version + ".DefinedStructureInfo"); 56 | Class blockPositionClass = Class.forName("net.minecraft.server." + version + ".BlockPosition"); 57 | Class nbtStreamToolsClass = Class.forName("net.minecraft.server." + version + ".NBTCompressedStreamTools"); 58 | definedStructureClass = Class.forName("net.minecraft.server." + version + ".DefinedStructure"); 59 | Class enumBlockRotationClass = Class.forName("net.minecraft.server." + version + ".EnumBlockRotation"); 60 | Class listNBTTagClass = Class.forName("net.minecraft.server." + version + ".NBTTagList"); 61 | Class chunkCoordIntPairClass = Class.forName("net.minecraft.server." + version + ".ChunkCoordIntPair"); 62 | Class craftBlockClass = Class.forName("org.bukkit.craftbukkit." + version + ".block.CraftBlock"); 63 | Class generatorAccessClass; 64 | if(version.startsWith("v1_15") || version.startsWith("v1_16_R1")) 65 | generatorAccessClass = Class.forName("net.minecraft.server." + version + ".GeneratorAccess"); 66 | else generatorAccessClass = Class.forName("net.minecraft.server." + version + ".WorldAccess"); 67 | nbtStreamToolsAccess = MethodAccess.get(nbtStreamToolsClass); 68 | loadNBTStreamFromInputStreamIndex = nbtStreamToolsAccess.getIndex("a", InputStream.class); 69 | definedStructureConstructorAccess = ConstructorAccess.get(definedStructureClass); 70 | definedStructureMethodAccess = MethodAccess.get(definedStructureClass); 71 | loadStructureMethodIndex = definedStructureMethodAccess.getIndex("b", compoundNBTTagClass); 72 | getStructureAsNBTMethodIndex = definedStructureMethodAccess.getIndex("a", compoundNBTTagClass); 73 | if(version.startsWith("v1_15")) 74 | pasteMethodIndex = definedStructureMethodAccess.getIndex("a", generatorAccessClass, blockPositionClass, definedStructureInfoClass); 75 | else 76 | pasteMethodIndex = definedStructureMethodAccess.getIndex("a", generatorAccessClass, blockPositionClass, definedStructureInfoClass, Random.class); 77 | compoundNBTConstructorAccess = ConstructorAccess.get(compoundNBTTagClass); 78 | compoundNBTTagMethodAccess = MethodAccess.get(compoundNBTTagClass); 79 | getNBTListMethodIndex = compoundNBTTagMethodAccess.getIndex("getList", String.class, int.class); 80 | listNBTTagMethodAccess = MethodAccess.get(listNBTTagClass); 81 | getNBTListItemMethodIndex = listNBTTagMethodAccess.getIndex("e", int.class); 82 | enumBlockRotationMethodAccess = MethodAccess.get(enumBlockRotationClass); 83 | enumBlockRotationValueOfIndex = enumBlockRotationMethodAccess.getIndex("valueOf", String.class); 84 | craftWorldMethodAccess = MethodAccess.get(craftWorldClass); 85 | getCraftWorldHandleIndex = craftWorldMethodAccess.getIndex("getHandle"); 86 | definedStructureInfoMethodAccess = MethodAccess.get(definedStructureInfoClass); 87 | definedStructureInfoConstructorAccess = ConstructorAccess.get(definedStructureInfoClass); 88 | setRotationMethodIndex = definedStructureInfoMethodAccess.getIndex("a", enumBlockRotationClass); 89 | mysteryBooleanMethodIndex = definedStructureInfoMethodAccess.getIndex("a", boolean.class); 90 | chunkCoordIntPairMethodIndex = definedStructureInfoMethodAccess.getIndex("a", chunkCoordIntPairClass); 91 | mysteryBooleancMethodIndex = definedStructureInfoMethodAccess.getIndex("c", boolean.class); 92 | setRandomMethodIndex = definedStructureInfoMethodAccess.getIndex("a", Random.class); 93 | craftBlockMethodAccess = MethodAccess.get(craftBlockClass); 94 | craftBlockGetPositionIndex = craftBlockMethodAccess.getIndex("getPosition"); 95 | Bukkit.getLogger().info("[Gaea] Finished reflections. Time elapsed: " + ((double) (System.nanoTime() - start)) / 1000000 + "ms"); 96 | } catch(ClassNotFoundException e) { 97 | Bukkit.getLogger().severe("[Gaea] An error occurred whilst initializing Reflection. Please report this."); 98 | e.printStackTrace(); 99 | Bukkit.getLogger().severe("[Gaea] Report the above error to Gaea!"); 100 | Bukkit.getLogger().severe("[Gaea] This is most likely caused by running the plugin on an unsupported version."); 101 | } 102 | } 103 | 104 | private int[] dimension; 105 | private Object structure; 106 | private Location origin; 107 | private int rotation = 0; 108 | 109 | /** 110 | * Load a structure from an InputStream. 111 | * 112 | * @param origin - The origin location of the structure. 113 | * @param file - The InputStream from which to load the structure. 114 | * @author dfsek 115 | * @since 3.5.0 116 | * @deprecated 117 | */ 118 | @Deprecated 119 | public NMSStructure(Location origin, InputStream file) { 120 | Object structure; 121 | try { 122 | structure = definedStructureConstructorAccess.newInstance(); 123 | definedStructureMethodAccess.invoke(structure, loadStructureMethodIndex, nbtStreamToolsAccess.invoke(null, loadNBTStreamFromInputStreamIndex, file)); 124 | Object tag = definedStructureMethodAccess.invoke(structure, getStructureAsNBTMethodIndex, compoundNBTConstructorAccess.newInstance()); 125 | Object dimTag = compoundNBTTagMethodAccess.invoke(tag, getNBTListMethodIndex, "size", 3); 126 | this.dimension = new int[] {(int) listNBTTagMethodAccess.invoke(dimTag, getNBTListItemMethodIndex, 0), 127 | (int) listNBTTagMethodAccess.invoke(dimTag, getNBTListItemMethodIndex, 1), 128 | (int) listNBTTagMethodAccess.invoke(dimTag, getNBTListItemMethodIndex, 2)}; 129 | this.structure = structure; 130 | this.origin = origin; 131 | } catch(IllegalArgumentException e) { 132 | e.printStackTrace(); 133 | } 134 | try { 135 | file.close(); 136 | } catch(IOException e) { 137 | e.printStackTrace(); 138 | } 139 | } 140 | 141 | /** 142 | * Load a structure from an Object. 143 | * 144 | * @param origin - The origin location of the structure. 145 | * @param file - The InputStream from which to load the structure. 146 | * @author dfsek 147 | * @since 4.0.0 148 | */ 149 | public NMSStructure(Location origin, Object file) { 150 | if(file.getClass() != definedStructureClass) 151 | throw new IllegalArgumentException("Object is not member of required class!"); 152 | try { 153 | Object tag = definedStructureMethodAccess.invoke(file, getStructureAsNBTMethodIndex, compoundNBTConstructorAccess.newInstance()); 154 | Object dimTag = compoundNBTTagMethodAccess.invoke(tag, getNBTListMethodIndex, "size", 3); 155 | this.dimension = new int[] {(int) listNBTTagMethodAccess.invoke(dimTag, getNBTListItemMethodIndex, 0), 156 | (int) listNBTTagMethodAccess.invoke(dimTag, getNBTListItemMethodIndex, 1), 157 | (int) listNBTTagMethodAccess.invoke(dimTag, getNBTListItemMethodIndex, 2)}; 158 | this.structure = file; 159 | this.origin = origin; 160 | } catch(IllegalArgumentException e) { 161 | e.printStackTrace(); 162 | } 163 | } 164 | 165 | /** 166 | * Gets the NBT Tag object from an InputStream.
167 | * Use for loading structure data into memory. 168 | * 169 | * @param file InputStream to load from. 170 | * @return Object - The NBT Tag object 171 | */ 172 | public static Object getAsTag(InputStream file) { 173 | Object structure; 174 | try { 175 | structure = definedStructureConstructorAccess.newInstance(); 176 | definedStructureMethodAccess.invoke(structure, loadStructureMethodIndex, nbtStreamToolsAccess.invoke(null, loadNBTStreamFromInputStreamIndex, file)); 177 | return structure; 178 | } catch(IllegalArgumentException e) { 179 | e.printStackTrace(); 180 | } 181 | return null; 182 | } 183 | 184 | /** 185 | * Loads the class, use to initialize reflections before generation begins. 186 | * 187 | * @author dfsek 188 | * @since 3.6.5 189 | */ 190 | public static void load() { 191 | } 192 | 193 | /** 194 | * Gets the origin of a structure. 195 | * 196 | * @return Location - The origin of the structure 197 | * @author dfsek 198 | * @since 2.0.0 199 | */ 200 | public Location getOrigin() { 201 | return this.origin; 202 | } 203 | 204 | public void setOrigin(Location origin) { 205 | this.origin = origin; 206 | } 207 | 208 | /** 209 | * Gets the dimensions of a structure. 210 | * 211 | * @return int[] - The X, Y, and Z dimensions of the structure 212 | * @author dfsek 213 | * @since 2.0.0 214 | */ 215 | public int[] getDimensions() { 216 | return this.dimension; 217 | } 218 | 219 | /** 220 | * Gets the rotation of a structure. 221 | * 222 | * @return int - The rotation of the structure 223 | * @author dfsek 224 | * @since 2.0.0 225 | */ 226 | public int getRotation() { 227 | return this.rotation; 228 | } 229 | 230 | /** 231 | * Sets the rotation of structure. 232 | * 233 | * @param rotation - The rotation (in degrees) 234 | * @author dfsek 235 | * @since 2.0.0 236 | */ 237 | public void setRotation(int rotation) { 238 | if(rotation % 90 != 0 || rotation > 360) 239 | throw new IllegalArgumentException("Invalid rotation provided. Rotation must be multiple of 90."); 240 | this.rotation = rotation; 241 | } 242 | 243 | /** 244 | * Gets the locations containing the structure. 245 | * 246 | * @return Location[] - The top and bottom bounding locations. 247 | * @author dfsek 248 | * @since 2.0.0 249 | */ 250 | public Location[] getBoundingLocations() { 251 | switch(this.rotation) { 252 | case 0: 253 | case 360: 254 | return new Location[] {this.origin, 255 | new Location(this.origin.getWorld(), this.origin.getX() + this.getX(), this.origin.getY() + this.getY(), this.origin.getZ() + this.getZ())}; 256 | case 90: 257 | return new Location[] {this.origin, 258 | new Location(this.origin.getWorld(), this.origin.getX() - this.getZ(), this.origin.getY() + this.getY(), this.origin.getZ() + this.getX())}; 259 | case 180: 260 | return new Location[] {this.origin, 261 | new Location(this.origin.getWorld(), this.origin.getX() - this.getX(), this.origin.getY() + this.getY(), this.origin.getZ() - this.getZ())}; 262 | case 270: 263 | return new Location[] {this.origin, 264 | new Location(this.origin.getWorld(), this.origin.getX() + this.getZ(), this.origin.getY() + this.getY(), this.origin.getZ() - this.getX())}; 265 | default: 266 | throw new IllegalArgumentException("Invalid rotation provided. Rotation must be multiple of 90."); 267 | } 268 | } 269 | 270 | /** 271 | * Gets the X dimension of a structure. 272 | * 273 | * @return int - The X dimension of the structure 274 | * @author dfsek 275 | * @since 2.0.0 276 | */ 277 | public int getX() { 278 | return this.dimension[0]; 279 | } 280 | 281 | /** 282 | * Gets the Y dimension of a structure. 283 | * 284 | * @return int - The Y dimension of the structure 285 | * @author dfsek 286 | * @since 2.0.0 287 | */ 288 | public int getY() { 289 | return this.dimension[1]; 290 | } 291 | 292 | /** 293 | * Gets the Z dimension of a structure. 294 | * 295 | * @return int - The Z dimension of the structure 296 | * @author dfsek 297 | * @since 2.0.0 298 | */ 299 | public int getZ() { 300 | return this.dimension[2]; 301 | } 302 | 303 | /** 304 | * Pastes a structure into the world. 305 | * 306 | * @author dfsek 307 | * @since 2.0.0 308 | */ 309 | public void paste() { 310 | try { 311 | Object rot; 312 | switch(this.rotation) { 313 | case 0: 314 | case 360: 315 | rot = enumBlockRotationMethodAccess.invoke(null, enumBlockRotationValueOfIndex, "NONE"); 316 | break; 317 | case 90: 318 | rot = enumBlockRotationMethodAccess.invoke(null, enumBlockRotationValueOfIndex, "CLOCKWISE_90"); 319 | break; 320 | case 180: 321 | rot = enumBlockRotationMethodAccess.invoke(null, enumBlockRotationValueOfIndex, "CLOCKWISE_180"); 322 | break; 323 | case 270: 324 | rot = enumBlockRotationMethodAccess.invoke(null, enumBlockRotationValueOfIndex, "COUNTERCLOCKWISE_90"); 325 | break; 326 | default: 327 | throw new IllegalArgumentException("Invalid rotation provided. Rotation must be multiple of 90."); 328 | } 329 | 330 | Object world = craftWorldMethodAccess.invoke(this.origin.getWorld(), getCraftWorldHandleIndex); 331 | Object info = definedStructureInfoMethodAccess.invoke(definedStructureInfoConstructorAccess.newInstance(), setRotationMethodIndex, rot); 332 | 333 | Object pos = craftBlockMethodAccess.invoke(this.origin.getBlock(), craftBlockGetPositionIndex); 334 | 335 | info = definedStructureInfoMethodAccess.invoke(info, mysteryBooleanMethodIndex, false); 336 | info = definedStructureInfoMethodAccess.invoke(info, chunkCoordIntPairMethodIndex, (Object) null); 337 | info = definedStructureInfoMethodAccess.invoke(info, mysteryBooleancMethodIndex, false); 338 | info = definedStructureInfoMethodAccess.invoke(info, setRandomMethodIndex, new FastRandom()); 339 | 340 | if(version.startsWith("v1_15")) { 341 | definedStructureMethodAccess.invoke(this.structure, pasteMethodIndex, world, pos, info); 342 | } else { 343 | definedStructureMethodAccess.invoke(this.structure, pasteMethodIndex, world, pos, info, new FastRandom()); 344 | } 345 | } catch(IllegalArgumentException e) { 346 | e.printStackTrace(); 347 | } 348 | } 349 | } 350 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/structures/Structure.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.structures; 2 | 3 | import org.bukkit.Location; 4 | import org.polydev.gaea.structures.features.Feature; 5 | import org.polydev.gaea.structures.spawn.StructureSpawnInfo; 6 | 7 | import java.util.List; 8 | import java.util.Random; 9 | 10 | public interface Structure { 11 | NMSStructure getInstance(Location origin, Random r); 12 | 13 | List getFeatures(); 14 | 15 | StructureSpawnInfo getSpawnInfo(); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/structures/StructureUtil.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.structures; 2 | 3 | import net.jafama.FastMath; 4 | import org.bukkit.Location; 5 | import org.bukkit.block.BlockState; 6 | import org.bukkit.block.Chest; 7 | import org.bukkit.block.Container; 8 | import org.bukkit.block.DoubleChest; 9 | import org.bukkit.inventory.InventoryHolder; 10 | import org.polydev.gaea.util.GlueList; 11 | 12 | import java.util.List; 13 | 14 | public class StructureUtil { 15 | public static List getChestsIn(Location minLoc, Location maxLoc) { 16 | List locations = new GlueList<>(); 17 | for(Location location : getLocationListBetween(minLoc, maxLoc)) { 18 | BlockState blockState = location.getBlock().getState(); 19 | if(blockState instanceof Container) { 20 | if(blockState instanceof Chest) { 21 | InventoryHolder holder = ((Chest) blockState).getInventory().getHolder(); 22 | if(holder instanceof DoubleChest) { 23 | DoubleChest doubleChest = ((DoubleChest) holder); 24 | Location leftSideLocation = ((Chest) doubleChest.getLeftSide()).getLocation(); 25 | Location rightSideLocation = ((Chest) doubleChest.getRightSide()).getLocation(); 26 | 27 | Location roundedLocation = new Location(location.getWorld(), FastMath.floor(location.getX()), FastMath.floor(location.getY()), FastMath.floor(location.getZ())); 28 | 29 | // Check to see if this (or the other) side of the chest is already 30 | // in the list 31 | if((leftSideLocation.distance(roundedLocation) < 1 && isNotAlreadyIn(locations, rightSideLocation)) 32 | || (rightSideLocation.distance(roundedLocation) < 1 && isNotAlreadyIn(locations, leftSideLocation))) { 33 | locations.add(roundedLocation); 34 | 35 | } 36 | 37 | } else if(holder instanceof Chest) { 38 | locations.add(location); 39 | } 40 | } else { 41 | locations.add(location); 42 | } 43 | } 44 | } 45 | return locations; 46 | } 47 | 48 | public static List getLocationListBetween(Location loc1, Location loc2) { 49 | int lowX = FastMath.min(loc1.getBlockX(), loc2.getBlockX()); 50 | int lowY = FastMath.min(loc1.getBlockY(), loc2.getBlockY()); 51 | int lowZ = FastMath.min(loc1.getBlockZ(), loc2.getBlockZ()); 52 | 53 | List locs = new GlueList<>(); 54 | for(int x = 0; x <= FastMath.abs(loc1.getBlockX() - loc2.getBlockX()); x++) { 55 | for(int y = 0; y <= FastMath.abs(loc1.getBlockY() - loc2.getBlockY()); y++) { 56 | for(int z = 0; z <= FastMath.abs(loc1.getBlockZ() - loc2.getBlockZ()); z++) { 57 | locs.add(new Location(loc1.getWorld(), (double) lowX + x, (double) lowY + y, (double) lowZ + z)); 58 | } 59 | } 60 | } 61 | return locs; 62 | } 63 | 64 | private static boolean isNotAlreadyIn(List locations, Location location) { 65 | for(Location auxLocation : locations) { 66 | if(location.distance(auxLocation) < 1) { 67 | return false; 68 | } 69 | } 70 | return true; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/structures/UserDefinedStructure.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.structures; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.Location; 5 | import org.polydev.gaea.structures.features.Feature; 6 | import org.polydev.gaea.structures.spawn.StructureSpawnInfo; 7 | 8 | import java.io.File; 9 | import java.io.FileInputStream; 10 | import java.io.FileNotFoundException; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | import java.util.Random; 15 | 16 | public class UserDefinedStructure implements Structure { 17 | private static final Map nmsContainer = new HashMap<>(); 18 | private final String id; 19 | private final List features; 20 | private final StructureSpawnInfo spawnInfo; 21 | 22 | public UserDefinedStructure(String id, File location, List features, StructureSpawnInfo spawnInfo) { 23 | this.id = id; 24 | this.features = features; 25 | this.spawnInfo = spawnInfo; 26 | try { 27 | nmsContainer.put(this, NMSStructure.getAsTag(new FileInputStream(location))); 28 | } catch(FileNotFoundException e) { 29 | e.printStackTrace(); 30 | Bukkit.getLogger().severe("[Gaea] Unable to load User Defined Structure " + id + "!"); 31 | } 32 | } 33 | 34 | public boolean isLoaded() { 35 | return nmsContainer.containsKey(this); 36 | } 37 | 38 | @Override 39 | public NMSStructure getInstance(Location origin, Random r) { 40 | return new NMSStructure(origin, nmsContainer.get(this)); 41 | } 42 | 43 | @Override 44 | public List getFeatures() { 45 | return this.features; 46 | } 47 | 48 | @Override 49 | public StructureSpawnInfo getSpawnInfo() { 50 | return spawnInfo; 51 | } 52 | 53 | @Override 54 | public String toString() { 55 | return "USER_DEF:" + id; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/structures/features/BlockReplaceFeature.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.structures.features; 2 | 3 | import org.bukkit.Location; 4 | import org.bukkit.Material; 5 | import org.polydev.gaea.math.ProbabilityCollection; 6 | import org.polydev.gaea.structures.NMSStructure; 7 | import org.polydev.gaea.util.WorldUtil; 8 | 9 | import java.util.Arrays; 10 | import java.util.List; 11 | import java.util.Random; 12 | 13 | public class BlockReplaceFeature implements Feature { 14 | private final ProbabilityCollection materials; 15 | private final double percent; 16 | private final List replaceable = Arrays.asList(Material.COBBLESTONE, Material.COBBLESTONE_STAIRS, Material.COBBLESTONE_SLAB, Material.COBBLESTONE_WALL, 17 | Material.STONE_BRICK_SLAB, Material.STONE_BRICK_STAIRS, Material.STONE_BRICK_WALL, Material.STONE_BRICKS, Material.OAK_WOOD, Material.OAK_LOG, Material.OAK_PLANKS, 18 | Material.OAK_FENCE, Material.OAK_FENCE_GATE, Material.OAK_SLAB, Material.OAK_STAIRS, Material.SPRUCE_FENCE, Material.SPRUCE_FENCE_GATE, Material.SPRUCE_LOG, Material.SPRUCE_PLANKS, 19 | Material.SPRUCE_SLAB, Material.SPRUCE_STAIRS); 20 | 21 | public BlockReplaceFeature(double percent, ProbabilityCollection materials) { 22 | this.materials = materials; 23 | this.percent = percent; 24 | } 25 | 26 | @Override 27 | public void populate(NMSStructure s, Random r) { 28 | List all = WorldUtil.getLocationListBetween(s.getBoundingLocations()[0], s.getBoundingLocations()[1]); 29 | for(int i = 0; i < all.size() * (percent / 100); i++) { 30 | boolean done = false; 31 | int attempts = 0; 32 | while(! done && attempts < 5) { 33 | Location candidate = all.get(r.nextInt(all.size())); 34 | if(replaceable.contains(candidate.getBlock().getType())) { 35 | candidate.getBlock().setType(materials.get(r)); 36 | done = true; 37 | } 38 | attempts++; 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/structures/features/EntityFeature.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.structures.features; 2 | 3 | import org.bukkit.Location; 4 | import org.bukkit.entity.EntityType; 5 | import org.polydev.gaea.structures.NMSStructure; 6 | import org.polydev.gaea.util.WorldUtil; 7 | 8 | import java.util.List; 9 | import java.util.Random; 10 | 11 | public class EntityFeature implements Feature { 12 | private final int min; 13 | private final int max; 14 | private final EntityType type; 15 | 16 | 17 | public EntityFeature(int min, int max, EntityType entity) { 18 | this.max = max; 19 | this.min = min; 20 | this.type = entity; 21 | } 22 | 23 | @Override 24 | public void populate(NMSStructure s, Random r) { 25 | int num = r.nextInt(max - min + 1) + min; 26 | List all = WorldUtil.getLocationListBetween(s.getBoundingLocations()[0], s.getBoundingLocations()[1]); 27 | for(int i = 0; i < num; i++) { 28 | boolean done = false; 29 | int attempts = 0; 30 | while(! done && attempts < 8) { 31 | Location candidate = all.get(r.nextInt(all.size())); 32 | if(candidate.getBlock().isEmpty()) { 33 | candidate.getWorld().spawnEntity(candidate, type); 34 | done = true; 35 | } 36 | attempts++; 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/structures/features/Feature.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.structures.features; 2 | 3 | import org.polydev.gaea.structures.NMSStructure; 4 | 5 | import java.util.Random; 6 | 7 | public interface Feature { 8 | void populate(NMSStructure s, Random r); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/structures/features/LootFeature.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.structures.features; 2 | 3 | import org.apache.commons.io.IOUtils; 4 | import org.bukkit.Location; 5 | import org.bukkit.Material; 6 | import org.bukkit.block.BlockState; 7 | import org.bukkit.block.Container; 8 | import org.bukkit.inventory.Inventory; 9 | import org.json.simple.parser.ParseException; 10 | import org.polydev.gaea.structures.NMSStructure; 11 | import org.polydev.gaea.structures.StructureUtil; 12 | import org.polydev.gaea.structures.loot.LootTable; 13 | 14 | import java.io.IOException; 15 | import java.io.InputStream; 16 | import java.nio.charset.StandardCharsets; 17 | import java.util.Random; 18 | 19 | public class LootFeature implements Feature { 20 | private final LootTable table; 21 | 22 | public LootFeature(InputStream lootFile) { 23 | LootTable table1; 24 | try { 25 | String loot = IOUtils.toString(lootFile, StandardCharsets.UTF_8.name()); 26 | table1 = new LootTable(loot); 27 | } catch(ParseException | IOException e) { 28 | e.printStackTrace(); 29 | table1 = null; 30 | } 31 | this.table = table1; 32 | } 33 | 34 | @Override 35 | public void populate(NMSStructure s, Random r) { 36 | for(Location chestLoc : StructureUtil.getChestsIn(s.getBoundingLocations()[0], s.getBoundingLocations()[1])) { 37 | BlockState blockState = chestLoc.getBlock().getState(); 38 | if(blockState instanceof Container && (chestLoc.getBlock().getType().equals(Material.CHEST) || chestLoc.getBlock().getType().equals(Material.TRAPPED_CHEST))) { 39 | Container container = (Container) blockState; 40 | Inventory containerInventory = container.getInventory(); 41 | this.table.fillInventory(containerInventory, r); 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/structures/features/PersistentDataFeature.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.structures.features; 2 | 3 | import org.bukkit.Location; 4 | import org.bukkit.Material; 5 | import org.bukkit.NamespacedKey; 6 | import org.bukkit.block.BlockState; 7 | import org.bukkit.block.Chest; 8 | import org.bukkit.persistence.PersistentDataType; 9 | import org.polydev.gaea.structures.NMSStructure; 10 | import org.polydev.gaea.structures.StructureUtil; 11 | 12 | import java.util.Random; 13 | 14 | public class PersistentDataFeature implements Feature { 15 | private final NamespacedKey key; 16 | 17 | public PersistentDataFeature(NamespacedKey key) { 18 | this.key = key; 19 | } 20 | 21 | @Override 22 | public void populate(NMSStructure s, Random r) { 23 | for(Location chestLoc : StructureUtil.getChestsIn(s.getBoundingLocations()[0], s.getBoundingLocations()[1])) { 24 | BlockState blockState = chestLoc.getBlock().getState(); 25 | if(blockState instanceof Chest && (chestLoc.getBlock().getType().equals(Material.CHEST) || chestLoc.getBlock().getType().equals(Material.TRAPPED_CHEST))) { 26 | Chest chest = (Chest) chestLoc.getBlock().getState(); 27 | chest.getPersistentDataContainer().set(key, PersistentDataType.INTEGER, (s.getRotation() / 90)); 28 | chest.update(); 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/structures/loot/Entry.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.structures.loot; 2 | 3 | import net.jafama.FastMath; 4 | import org.bukkit.Material; 5 | import org.bukkit.inventory.ItemStack; 6 | import org.json.simple.JSONArray; 7 | import org.json.simple.JSONObject; 8 | import org.polydev.gaea.structures.loot.functions.AmountFunction; 9 | import org.polydev.gaea.structures.loot.functions.DamageFunction; 10 | import org.polydev.gaea.structures.loot.functions.EnchantWithLevelsFunction; 11 | import org.polydev.gaea.structures.loot.functions.Function; 12 | import org.polydev.gaea.util.GlueList; 13 | 14 | import java.util.List; 15 | import java.util.Random; 16 | 17 | /** 18 | * Representation of a single item entry within a Loot Table pool. 19 | */ 20 | public class Entry { 21 | private final Material item; 22 | private final long weight; 23 | private final List functions = new GlueList<>(); 24 | 25 | /** 26 | * Instantiates an Entry from a JSON representation. 27 | * 28 | * @param entry The JSON Object to instantiate from. 29 | */ 30 | public Entry(JSONObject entry) { 31 | 32 | String id = entry.get("name").toString(); 33 | this.item = Material.matchMaterial(id); 34 | 35 | long weight1; 36 | try { 37 | weight1 = (long) entry.get("weight"); 38 | } catch(NullPointerException e) { 39 | weight1 = 1; 40 | } 41 | 42 | this.weight = weight1; 43 | if(entry.containsKey("functions")) { 44 | for(Object function : (JSONArray) entry.get("functions")) { 45 | switch(((String) ((JSONObject) function).get("function"))) { 46 | case "minecraft:set_count": 47 | case "set_count": 48 | Object loot = ((JSONObject) function).get("count"); 49 | long max, min; 50 | if(loot instanceof Long) { 51 | max = (Long) loot; 52 | min = (Long) loot; 53 | } else { 54 | max = (long) ((JSONObject) loot).get("max"); 55 | min = (long) ((JSONObject) loot).get("min"); 56 | } 57 | functions.add(new AmountFunction(FastMath.toIntExact(min), FastMath.toIntExact(max))); 58 | break; 59 | case "minecraft:set_damage": 60 | case "set_damage": 61 | long maxDamage = (long) ((JSONObject) ((JSONObject) function).get("damage")).get("max"); 62 | long minDamage = (long) ((JSONObject) ((JSONObject) function).get("damage")).get("min"); 63 | functions.add(new DamageFunction(FastMath.toIntExact(minDamage), FastMath.toIntExact(maxDamage))); 64 | break; 65 | case "minecraft:enchant_with_levels": 66 | case "enchant_with_levels": 67 | long maxEnchant = (long) ((JSONObject) ((JSONObject) function).get("levels")).get("max"); 68 | long minEnchant = (long) ((JSONObject) ((JSONObject) function).get("levels")).get("min"); 69 | JSONArray disabled = null; 70 | if(((JSONObject) function).containsKey("disabled_enchants")) 71 | disabled = (JSONArray) ((JSONObject) function).get("disabled_enchants"); 72 | functions.add(new EnchantWithLevelsFunction(FastMath.toIntExact(minEnchant), FastMath.toIntExact(maxEnchant), disabled)); 73 | break; 74 | } 75 | } 76 | } 77 | } 78 | 79 | /** 80 | * Fetches a single ItemStack from the Entry, applying all functions to it. 81 | * 82 | * @param r The Random instance to apply functions with 83 | * @return ItemStack - The ItemStack with all functions applied. 84 | */ 85 | public ItemStack getItem(Random r) { 86 | ItemStack item = new ItemStack(this.item, 1); 87 | for(Function f : functions) { 88 | item = f.apply(item, r); 89 | } 90 | return item; 91 | } 92 | 93 | /** 94 | * Gets the weight attribute of the Entry. 95 | * 96 | * @return long - The weight of the Entry. 97 | */ 98 | public long getWeight() { 99 | return this.weight; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/structures/loot/LootTable.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.structures.loot; 2 | 3 | import org.bukkit.inventory.Inventory; 4 | import org.bukkit.inventory.ItemStack; 5 | import org.json.simple.JSONArray; 6 | import org.json.simple.JSONObject; 7 | import org.json.simple.parser.JSONParser; 8 | import org.json.simple.parser.ParseException; 9 | import org.polydev.gaea.util.GlueList; 10 | 11 | import java.util.List; 12 | import java.util.Random; 13 | 14 | /** 15 | * Class representation of a Loot Table to populate chest loot. 16 | */ 17 | public class LootTable { 18 | private final List pools = new GlueList<>(); 19 | 20 | /** 21 | * Instantiates a LootTable from a JSON String. 22 | * 23 | * @param json The JSON String representing the loot table. 24 | * @throws ParseException if malformed JSON is passed. 25 | */ 26 | public LootTable(String json) throws ParseException { 27 | JSONParser jsonParser = new JSONParser(); 28 | Object tableJSON = jsonParser.parse(json); 29 | JSONArray poolArray = (JSONArray) ((JSONObject) tableJSON).get("pools"); 30 | for(Object pool : poolArray) { 31 | pools.add(new Pool((JSONObject) pool)); 32 | } 33 | } 34 | 35 | /** 36 | * Fetches a list of ItemStacks from the loot table using the given Random instance. 37 | * 38 | * @param r The Random instance to use. 39 | * @return List<ItemStack> - The list of loot fetched. 40 | */ 41 | public List getLoot(Random r) { 42 | List itemList = new GlueList<>(); 43 | for(Pool pool : pools) { 44 | itemList.addAll(pool.getItems(r)); 45 | } 46 | return itemList; 47 | } 48 | 49 | /** 50 | * Fills an Inventory with loot. 51 | * 52 | * @param i The Inventory to fill. 53 | * @param r The The Random instance to use. 54 | */ 55 | public void fillInventory(Inventory i, Random r) { 56 | List loot = getLoot(r); 57 | for(ItemStack stack : loot) { 58 | int attempts = 0; 59 | while(stack.getAmount() != 0 && attempts < 10) { 60 | ItemStack newStack = stack.clone(); 61 | newStack.setAmount(1); 62 | int slot = r.nextInt(i.getSize()); 63 | ItemStack slotItem = i.getItem(slot); 64 | if(slotItem == null) { 65 | i.setItem(slot, newStack); 66 | stack.setAmount(stack.getAmount() - 1); 67 | } else if(slotItem.getType() == newStack.getType()) { 68 | ItemStack dep = newStack.clone(); 69 | dep.setAmount(newStack.getAmount() + slotItem.getAmount()); 70 | i.setItem(slot, dep); 71 | stack.setAmount(stack.getAmount() - 1); 72 | } 73 | attempts++; 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/structures/loot/Pool.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.structures.loot; 2 | 3 | import net.jafama.FastMath; 4 | import org.bukkit.inventory.ItemStack; 5 | import org.json.simple.JSONArray; 6 | import org.json.simple.JSONObject; 7 | import org.polydev.gaea.math.ProbabilityCollection; 8 | import org.polydev.gaea.util.GlueList; 9 | 10 | import java.util.List; 11 | import java.util.Random; 12 | 13 | /** 14 | * Representation of a Loot Table pool, or a set of items to be fetched independently. 15 | */ 16 | public class Pool { 17 | private final int max; 18 | private final int min; 19 | private final ProbabilityCollection entries = new ProbabilityCollection<>(); 20 | 21 | /** 22 | * Instantiates a Pool from a JSON representation. 23 | * 24 | * @param pool The JSON Object to instantiate from. 25 | */ 26 | public Pool(JSONObject pool) { 27 | Object amount = pool.get("rolls"); 28 | if(amount instanceof Long) { 29 | max = FastMath.toIntExact((Long) amount); 30 | min = FastMath.toIntExact((Long) amount); 31 | } else { 32 | max = FastMath.toIntExact((Long) ((JSONObject) amount).get("max")); 33 | min = FastMath.toIntExact((Long) ((JSONObject) amount).get("min")); 34 | } 35 | 36 | for(Object entryJSON : (JSONArray) pool.get("entries")) { 37 | Entry entry = new Entry((JSONObject) entryJSON); 38 | entries.add(entry, FastMath.toIntExact(entry.getWeight())); 39 | } 40 | } 41 | 42 | /** 43 | * Fetches a list of items from the pool using the provided Random instance. 44 | * 45 | * @param r The Random instance to use. 46 | * @return List<ItemStack> - The list of items fetched. 47 | */ 48 | public List getItems(Random r) { 49 | 50 | int rolls = r.nextInt(max - min + 1) + min; 51 | List items = new GlueList<>(); 52 | for(int i = 0; i < rolls; i++) { 53 | items.add(entries.get(r).getItem(r)); 54 | } 55 | return items; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/structures/loot/functions/AmountFunction.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.structures.loot.functions; 2 | 3 | import org.bukkit.inventory.ItemStack; 4 | 5 | import java.util.Random; 6 | 7 | /** 8 | * Loot Function fot setting the amount of an item. 9 | */ 10 | public class AmountFunction implements Function { 11 | private final int max; 12 | private final int min; 13 | 14 | /** 15 | * Instantiates an AmountFunction. 16 | * 17 | * @param min Minimum amount. 18 | * @param max Maximum amount. 19 | */ 20 | public AmountFunction(int min, int max) { 21 | this.min = min; 22 | this.max = max; 23 | } 24 | 25 | /** 26 | * Applies the function to an ItemStack. 27 | * 28 | * @param original The ItemStack on which to apply the function. 29 | * @param r The Random instance to use. 30 | * @return - ItemStack - The mutated ItemStack. 31 | */ 32 | @Override 33 | public ItemStack apply(ItemStack original, Random r) { 34 | original.setAmount(r.nextInt(max - min + 1) + min); 35 | return original; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/structures/loot/functions/DamageFunction.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.structures.loot.functions; 2 | 3 | import org.bukkit.inventory.ItemStack; 4 | import org.bukkit.inventory.meta.Damageable; 5 | import org.bukkit.inventory.meta.ItemMeta; 6 | 7 | import java.util.Random; 8 | 9 | /** 10 | * Loot Function for setting the damage on items in Loot Tables 11 | */ 12 | public class DamageFunction implements Function { 13 | private final int max; 14 | private final int min; 15 | 16 | /** 17 | * Instantiates a DamageFunction. 18 | * 19 | * @param min Minimum amount of damage (percentage, out of 100) 20 | * @param max Maximum amount of damage (percentage, out of 100) 21 | */ 22 | public DamageFunction(int min, int max) { 23 | this.min = min; 24 | this.max = max; 25 | } 26 | 27 | /** 28 | * Applies the function to an ItemStack. 29 | * 30 | * @param original The ItemStack on which to apply the function. 31 | * @param r The Random instance to use. 32 | * @return - ItemStack - The mutated ItemStack. 33 | */ 34 | @Override 35 | public ItemStack apply(ItemStack original, Random r) { 36 | double itemDurability = (r.nextDouble() * (max - min)) + min; 37 | Damageable damage = (Damageable) original.getItemMeta(); 38 | damage.setDamage((int) (original.getType().getMaxDurability() - (itemDurability / 100) * original.getType().getMaxDurability())); 39 | original.setItemMeta((ItemMeta) damage); 40 | return original; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/structures/loot/functions/EnchantWithLevelsFunction.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.structures.loot.functions; 2 | 3 | import net.jafama.FastMath; 4 | import org.bukkit.Bukkit; 5 | import org.bukkit.enchantments.Enchantment; 6 | import org.bukkit.inventory.ItemStack; 7 | import org.json.simple.JSONArray; 8 | import org.polydev.gaea.util.GlueList; 9 | 10 | import java.util.Collections; 11 | import java.util.List; 12 | import java.util.Random; 13 | 14 | public class EnchantWithLevelsFunction implements Function { 15 | private final int min; 16 | private final int max; 17 | private final JSONArray disabled; 18 | 19 | 20 | public EnchantWithLevelsFunction(int min, int max, JSONArray disabled) { 21 | this.max = max; 22 | this.min = min; 23 | this.disabled = disabled; 24 | } 25 | 26 | /** 27 | * Applies the function to an ItemStack. 28 | * 29 | * @param original The ItemStack on which to apply the function. 30 | * @param r The Random instance to use. 31 | * @return - ItemStack - The mutated ItemStack. 32 | */ 33 | @Override 34 | public ItemStack apply(ItemStack original, Random r) { 35 | double enchant = (r.nextDouble() * (max - min)) + min; 36 | List possible = new GlueList<>(); 37 | for(Enchantment ench : Enchantment.values()) { 38 | if(ench.canEnchantItem(original) && (disabled == null || !this.disabled.contains(ench.getName()))) { 39 | possible.add(ench); 40 | } 41 | } 42 | int numEnchant = (r.nextInt((int) FastMath.abs(enchant)) / 10 + 1); 43 | if(possible.size() >= numEnchant) { 44 | Collections.shuffle(possible); 45 | iter: 46 | for(int i = 0; i < numEnchant; i++) { 47 | Enchantment chosen = possible.get(i); 48 | for(Enchantment ench : original.getEnchantments().keySet()) { 49 | if(chosen.conflictsWith(ench)) continue iter; 50 | } 51 | int lvl = r.nextInt(1 + (int) (((enchant / 40 > 1) ? 1 : enchant / 40) * (chosen.getMaxLevel()))); 52 | try { 53 | original.addEnchantment(chosen, FastMath.max(lvl, 1)); 54 | } catch(IllegalArgumentException e) { 55 | Bukkit.getLogger().warning("[Gaea] Attempted to enchant " + original.getType() + " with " + chosen + " at level " + FastMath.max(lvl, 1) + ", but an unexpected exception occurred! Usually this is caused by a misbehaving enchantment plugin."); 56 | } 57 | } 58 | } 59 | return original; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/structures/loot/functions/Function.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.structures.loot.functions; 2 | 3 | import org.bukkit.inventory.ItemStack; 4 | 5 | import java.util.Random; 6 | 7 | /** 8 | * Interface for mutating items in Loot Tables. 9 | */ 10 | public interface Function { 11 | /** 12 | * Applies the function to an ItemStack. 13 | * 14 | * @param original The ItemStack on which to apply the function. 15 | * @param r The Random instance to use. 16 | * @return - ItemStack - The mutated ItemStack. 17 | */ 18 | ItemStack apply(ItemStack original, Random r); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/structures/spawn/AirSpawn.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.structures.spawn; 2 | 3 | import org.bukkit.Location; 4 | import org.polydev.gaea.structures.NMSStructure; 5 | 6 | import java.util.Random; 7 | 8 | public class AirSpawn implements StructureSpawnInfo { 9 | private final int height; 10 | private final int deviation; 11 | 12 | public AirSpawn(int height, int deviation) { 13 | this.height = height; 14 | this.deviation = deviation; 15 | } 16 | 17 | @Override 18 | public Location getSpawnLocation(Location init, Random r) { 19 | return new Location(init.getWorld(), init.getX(), height + ((deviation <= 0) ? 0 : r.nextInt(deviation) - ((double) deviation / 2)), init.getZ()); 20 | } 21 | 22 | @Override 23 | public boolean isValidSpawn(NMSStructure s) { 24 | return true; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/structures/spawn/GroundSpawn.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.structures.spawn; 2 | 3 | import org.bukkit.Location; 4 | import org.polydev.gaea.structures.NMSStructure; 5 | 6 | import java.util.Random; 7 | 8 | public class GroundSpawn implements StructureSpawnInfo { 9 | private final int offset; 10 | 11 | public GroundSpawn(int offset) { 12 | this.offset = offset; 13 | } 14 | 15 | @Override 16 | public Location getSpawnLocation(Location init, Random r) { 17 | return init.clone().add(0, offset, 0); 18 | } 19 | 20 | @Override 21 | public boolean isValidSpawn(NMSStructure s) { 22 | Location[] bounds = s.getBoundingLocations(); 23 | return (! bounds[0].clone().subtract(0, offset + 1, 0).getBlock().isEmpty()) 24 | && (! new Location(bounds[0].getWorld(), bounds[1].getX(), bounds[0].getY() - (offset + 1), bounds[0].getZ()).getBlock().isEmpty()) 25 | && (! new Location(bounds[0].getWorld(), bounds[0].getX(), bounds[0].getY() - (offset + 1), bounds[1].getZ()).getBlock().isEmpty()) 26 | && (! new Location(bounds[0].getWorld(), bounds[1].getX(), bounds[0].getY() - (offset + 1), bounds[1].getZ()).getBlock().isEmpty()); 27 | } 28 | } -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/structures/spawn/StructureSpawnInfo.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.structures.spawn; 2 | 3 | import org.bukkit.Location; 4 | import org.polydev.gaea.structures.NMSStructure; 5 | 6 | import java.util.Random; 7 | 8 | public interface StructureSpawnInfo { 9 | Location getSpawnLocation(Location init, Random r); 10 | 11 | boolean isValidSpawn(NMSStructure s); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/structures/spawn/UndergroundSpawn.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.structures.spawn; 2 | 3 | import org.bukkit.Location; 4 | import org.polydev.gaea.structures.NMSStructure; 5 | 6 | import java.util.Random; 7 | 8 | public class UndergroundSpawn implements StructureSpawnInfo { 9 | private final int depth; 10 | 11 | public UndergroundSpawn(int depth) { 12 | this.depth = depth; 13 | } 14 | 15 | @Override 16 | public Location getSpawnLocation(Location init, Random r) { 17 | return init.clone().subtract(0, - r.nextInt(depth / 2) - ((double) depth / 2), 0); 18 | } 19 | 20 | @Override 21 | public boolean isValidSpawn(NMSStructure s) { 22 | Location[] bounds = s.getBoundingLocations(); 23 | return (! bounds[0].getBlock().isPassable()) 24 | && (! bounds[1].getBlock().isPassable()) 25 | && (! new Location(bounds[0].getWorld(), bounds[1].getX(), bounds[0].getY(), bounds[0].getZ()).getBlock().isPassable()) 26 | && (! new Location(bounds[0].getWorld(), bounds[0].getX(), bounds[0].getY(), bounds[1].getZ()).getBlock().isPassable()) 27 | && (! new Location(bounds[0].getWorld(), bounds[1].getX(), bounds[0].getY(), bounds[1].getZ()).getBlock().isPassable()) 28 | && (! new Location(bounds[0].getWorld(), bounds[1].getX(), bounds[1].getY(), bounds[0].getZ()).getBlock().isPassable()) 29 | && (! new Location(bounds[0].getWorld(), bounds[0].getX(), bounds[1].getY(), bounds[1].getZ()).getBlock().isPassable()) 30 | && (! new Location(bounds[0].getWorld(), bounds[1].getX(), bounds[1].getY(), bounds[1].getZ()).getBlock().isPassable()); 31 | } 32 | } -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/tree/CustomTreeType.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.tree; 2 | 3 | import org.bukkit.Location; 4 | import org.polydev.gaea.tree.fractal.FractalTree; 5 | import org.polydev.gaea.tree.fractal.TreeGetter; 6 | import org.polydev.gaea.tree.fractal.trees.*; 7 | 8 | import java.util.Random; 9 | 10 | public enum CustomTreeType implements TreeGetter { 11 | SHATTERED_SMALL { 12 | @Override 13 | public FractalTree getTree(Location l, Random r) { 14 | return new SmallShatteredTree(l, r); 15 | } 16 | }, 17 | SHATTERED_LARGE { 18 | @Override 19 | public FractalTree getTree(Location l, Random r) { 20 | return new ShatteredTree(l, r); 21 | } 22 | }, 23 | GIANT_OAK { 24 | @Override 25 | public FractalTree getTree(Location l, Random r) { 26 | return new OakTree(l, r); 27 | } 28 | }, 29 | GIANT_SPRUCE { 30 | @Override 31 | public FractalTree getTree(Location l, Random r) { 32 | return new SpruceTree(l, r); 33 | } 34 | }, 35 | SMALL_SHATTERED_PILLAR { 36 | @Override 37 | public FractalTree getTree(Location l, Random r) { 38 | return new SmallShatteredPillar(l, r); 39 | } 40 | }, 41 | LARGE_SHATTERED_PILLAR { 42 | @Override 43 | public FractalTree getTree(Location l, Random r) { 44 | return new ShatteredPillar(l, r); 45 | } 46 | }, 47 | CACTUS { 48 | @Override 49 | public FractalTree getTree(Location l, Random r) { 50 | return new Cactus(l, r); 51 | } 52 | }, 53 | ICE_SPIKE { 54 | @Override 55 | public FractalTree getTree(Location l, Random r) { 56 | return new IceSpike(l, r); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/tree/Tree.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.tree; 2 | 3 | import org.bukkit.Location; 4 | import org.bukkit.Material; 5 | import org.bukkit.plugin.java.JavaPlugin; 6 | 7 | import java.util.Random; 8 | import java.util.Set; 9 | 10 | public interface Tree { 11 | boolean plant(Location l, Random r, JavaPlugin main); 12 | 13 | Set getSpawnable(); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/tree/TreeType.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.tree; 2 | 3 | import com.google.common.collect.Sets; 4 | import org.bukkit.Location; 5 | import org.bukkit.Material; 6 | import org.bukkit.plugin.java.JavaPlugin; 7 | import org.polydev.gaea.tree.fractal.FractalTree; 8 | 9 | import java.util.Collections; 10 | import java.util.Random; 11 | import java.util.Set; 12 | 13 | public enum TreeType implements Tree { 14 | SHATTERED_SMALL(null, Collections.singleton(Material.END_STONE)), 15 | SHATTERED_LARGE(null, Collections.singleton(Material.END_STONE)), 16 | GIANT_OAK(null, Sets.newHashSet(Material.GRASS_BLOCK, Material.DIRT, Material.PODZOL)), 17 | GIANT_SPRUCE(null, Sets.newHashSet(Material.GRASS_BLOCK, Material.DIRT, Material.PODZOL)), 18 | SMALL_SHATTERED_PILLAR(null, Collections.singleton(Material.END_STONE)), 19 | LARGE_SHATTERED_PILLAR(null, Collections.singleton(Material.END_STONE)), 20 | CACTUS(null, Sets.newHashSet(Material.SAND, Material.RED_SAND)), 21 | ICE_SPIKE(null, Sets.newHashSet(Material.SNOW_BLOCK, Material.SNOW, Material.STONE, Material.GRASS_BLOCK)), 22 | OAK(org.bukkit.TreeType.TREE, Sets.newHashSet(Material.GRASS_BLOCK, Material.DIRT, Material.PODZOL)), 23 | LARGE_OAK(org.bukkit.TreeType.BIG_TREE, Sets.newHashSet(Material.GRASS_BLOCK, Material.DIRT, Material.PODZOL)), 24 | SPRUCE(org.bukkit.TreeType.REDWOOD, Sets.newHashSet(Material.GRASS_BLOCK, Material.DIRT, Material.PODZOL)), 25 | LARGE_SPRUCE(org.bukkit.TreeType.TALL_REDWOOD, Sets.newHashSet(Material.GRASS_BLOCK, Material.DIRT, Material.PODZOL)), 26 | MEGA_SPRUCE(org.bukkit.TreeType.MEGA_REDWOOD, Sets.newHashSet(Material.GRASS_BLOCK, Material.DIRT, Material.PODZOL)), 27 | BIRCH(org.bukkit.TreeType.BIRCH, Sets.newHashSet(Material.GRASS_BLOCK, Material.DIRT, Material.PODZOL)), 28 | CHORUS_PLANT(org.bukkit.TreeType.CHORUS_PLANT, Sets.newHashSet(Material.END_STONE)), 29 | ACACIA(org.bukkit.TreeType.ACACIA, Sets.newHashSet(Material.GRASS_BLOCK, Material.DIRT, Material.PODZOL)), 30 | TALL_BIRCH(org.bukkit.TreeType.TALL_BIRCH, Sets.newHashSet(Material.GRASS_BLOCK, Material.DIRT, Material.PODZOL)), 31 | JUNGLE(org.bukkit.TreeType.JUNGLE, Sets.newHashSet(Material.GRASS_BLOCK, Material.DIRT, Material.PODZOL)), 32 | SMALL_JUNGLE(org.bukkit.TreeType.SMALL_JUNGLE, Sets.newHashSet(Material.GRASS_BLOCK, Material.DIRT, Material.PODZOL)), 33 | JUNGLE_COCOA(org.bukkit.TreeType.COCOA_TREE, Sets.newHashSet(Material.GRASS_BLOCK, Material.DIRT, Material.PODZOL)), 34 | JUNGLE_BUSH(org.bukkit.TreeType.JUNGLE_BUSH, Sets.newHashSet(Material.GRASS_BLOCK, Material.DIRT, Material.PODZOL)), 35 | DARK_OAK(org.bukkit.TreeType.DARK_OAK, Sets.newHashSet(Material.GRASS_BLOCK, Material.DIRT, Material.PODZOL)), 36 | BROWN_MUSHROOM(org.bukkit.TreeType.BROWN_MUSHROOM, Sets.newHashSet(Material.GRASS_BLOCK, Material.DIRT, Material.PODZOL, Material.MYCELIUM, Material.NETHERRACK, Material.CRIMSON_NYLIUM, Material.WARPED_NYLIUM)), 37 | RED_MUSHROOM(org.bukkit.TreeType.RED_MUSHROOM, Sets.newHashSet(Material.GRASS_BLOCK, Material.DIRT, Material.PODZOL, Material.MYCELIUM, Material.NETHERRACK, Material.CRIMSON_NYLIUM, Material.WARPED_NYLIUM)), 38 | SWAMP_OAK(org.bukkit.TreeType.SWAMP, Sets.newHashSet(Material.GRASS_BLOCK, Material.DIRT, Material.PODZOL)), 39 | WARPED_FUNGUS(org.bukkit.TreeType.WARPED_FUNGUS, Collections.singleton(Material.WARPED_NYLIUM)), 40 | CRIMSON_FUNGUS(org.bukkit.TreeType.CRIMSON_FUNGUS, Collections.singleton(Material.CRIMSON_NYLIUM)); 41 | 42 | private final org.bukkit.TreeType vanillaType; 43 | private final Set spawnable; 44 | 45 | TreeType(org.bukkit.TreeType vanillaType, Set spawnable) { 46 | this.vanillaType = vanillaType; 47 | this.spawnable = spawnable; 48 | } 49 | 50 | public static TreeType fromBukkit(org.bukkit.TreeType type) { 51 | switch(type) { 52 | case TREE: return OAK; 53 | case BIRCH: return BIRCH; 54 | case ACACIA: return ACACIA; 55 | case SWAMP: return SWAMP_OAK; 56 | case JUNGLE: return JUNGLE; 57 | case REDWOOD: return SPRUCE; 58 | case BIG_TREE: return LARGE_OAK; 59 | case DARK_OAK: return DARK_OAK; 60 | case COCOA_TREE: return JUNGLE_COCOA; 61 | case TALL_BIRCH: return TALL_BIRCH; 62 | case JUNGLE_BUSH: return JUNGLE_BUSH; 63 | case CHORUS_PLANT: return CHORUS_PLANT; 64 | case MEGA_REDWOOD: return MEGA_SPRUCE; 65 | case RED_MUSHROOM: return RED_MUSHROOM; 66 | case SMALL_JUNGLE: return SMALL_JUNGLE; 67 | case TALL_REDWOOD: return LARGE_SPRUCE; 68 | case WARPED_FUNGUS: return WARPED_FUNGUS; 69 | case BROWN_MUSHROOM: return BROWN_MUSHROOM; 70 | case CRIMSON_FUNGUS: return CRIMSON_FUNGUS; 71 | default: throw new IllegalArgumentException(); 72 | } 73 | } 74 | 75 | public boolean isCustom() { 76 | return this.vanillaType == null; 77 | } 78 | 79 | public org.bukkit.TreeType getVanillaTreeType() { 80 | return vanillaType; 81 | } 82 | 83 | public CustomTreeType getCustomTreeType() { 84 | if(getVanillaTreeType() != null) return null; 85 | return CustomTreeType.valueOf(this.toString()); 86 | } 87 | 88 | public boolean plant(Location l, Random r, JavaPlugin main) { 89 | if(this.getVanillaTreeType() == null) { 90 | if(!spawnable.contains(l.subtract(0, 1, 0).getBlock().getType())) return false; 91 | FractalTree tree = getCustomTreeType().getTree(l, r); 92 | if(main.isEnabled()) co.aikar.taskchain.BukkitTaskChainFactory.create(main).newChain() 93 | .async(tree::grow) 94 | .sync(tree::plant) 95 | .execute(); 96 | return true; 97 | } 98 | return l.getWorld().generateTree(l, this.getVanillaTreeType()); 99 | } 100 | 101 | @Override 102 | public Set getSpawnable() { 103 | return spawnable; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/tree/fractal/EntitySpawnHolder.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.tree.fractal; 2 | 3 | import org.bukkit.Location; 4 | import org.bukkit.entity.Entity; 5 | import org.bukkit.util.Consumer; 6 | 7 | 8 | public class EntitySpawnHolder { 9 | private final Location l; 10 | private final Class e; 11 | private final Consumer c; 12 | 13 | public EntitySpawnHolder(Location l, Class e, Consumer c) { 14 | this.l = l; 15 | this.e = e; 16 | this.c = c; 17 | } 18 | 19 | @SuppressWarnings("rawtypes") 20 | public Class getEntity() { 21 | return e; 22 | } 23 | 24 | public Consumer getConsumer() { 25 | return c; 26 | } 27 | 28 | public Location getLocation() { 29 | return l; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/tree/fractal/FractalTree.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.tree.fractal; 2 | 3 | import org.bukkit.Location; 4 | import org.bukkit.Material; 5 | import org.bukkit.block.data.BlockData; 6 | import org.bukkit.entity.Entity; 7 | import org.bukkit.util.Consumer; 8 | import org.polydev.gaea.util.GlueList; 9 | 10 | import java.util.*; 11 | 12 | 13 | public abstract class FractalTree { 14 | private final Map treeAssembler = new HashMap<>(); 15 | private final List entities = new GlueList<>(); 16 | private final Location origin; 17 | private final Random random; 18 | private final List replaceable = Arrays.asList(Material.AIR, Material.GRASS_BLOCK, Material.DIRT, Material.STONE, Material.COARSE_DIRT, Material.GRAVEL, Material.PODZOL, 19 | Material.GRASS, Material.TALL_GRASS, Material.FERN, Material.POPPY, Material.LARGE_FERN, Material.BLUE_ORCHID, Material.AZURE_BLUET, Material.END_STONE, Material.SNOW, Material.SAND, Material.STONE_BUTTON, Material.DEAD_BUSH); 20 | 21 | 22 | /** 23 | * Instantiates a TreeGrower at an origin location. 24 | * 25 | * @param origin - The origin location. 26 | * @param random - The random object to use whilst generating the tree. 27 | */ 28 | public FractalTree(Location origin, Random random) { 29 | this.origin = origin.add(0, 1, 0); 30 | this.random = random; 31 | } 32 | 33 | /** 34 | * Gets the raw tree map. 35 | * 36 | * @return HashMap<Location, BlockData> - The raw dictionary representation of the tree. 37 | */ 38 | public Map getTree() { 39 | return treeAssembler; 40 | } 41 | 42 | 43 | /** 44 | * Fetches the Random object used to generate the tree. 45 | * 46 | * @return Random - The Random object. 47 | */ 48 | public Random getRandom() { 49 | return random; 50 | } 51 | 52 | /** 53 | * Fetches the origin location. 54 | * 55 | * @return Location - The origin location specified upon instantiation. 56 | */ 57 | public Location getOrigin() { 58 | return origin; 59 | } 60 | 61 | /** 62 | * Sets a block in the tree's storage map to a material. 63 | * 64 | * @param l - The location to set. 65 | * @param m - The material to which it will be set. 66 | */ 67 | public void setBlock(Location l, Material m) { 68 | treeAssembler.put(l, m.createBlockData()); 69 | } 70 | 71 | /** 72 | * Grows the tree in memory. Intended to be invoked from an async thread. 73 | */ 74 | public abstract void grow(); 75 | 76 | /** 77 | * Pastes the tree in the world. Must be invoked from main thread. 78 | */ 79 | @SuppressWarnings("unchecked") 80 | public void plant() { 81 | for(Map.Entry entry : treeAssembler.entrySet()) { 82 | if(replaceable.contains(entry.getKey().getBlock().getType())) 83 | entry.getKey().getBlock().setBlockData(entry.getValue(), false); 84 | } 85 | for(EntitySpawnHolder e : entities) { 86 | Objects.requireNonNull(e.getLocation().getWorld()).spawn(e.getLocation(), e.getEntity(), e.getConsumer()); 87 | } 88 | } 89 | 90 | @SuppressWarnings("rawtypes, unchecked") 91 | public void spawnEntity(Location spawn, Class clazz, Consumer consumer) { 92 | entities.add(new EntitySpawnHolder(spawn, clazz, consumer)); 93 | } 94 | 95 | /** 96 | * Gets the material at the specified block. 97 | * Returns air if no material has been set. 98 | * 99 | * @param l - The location at which to check. 100 | * @return Material - The material at the specified block. 101 | */ 102 | public Material getMaterial(Location l) { 103 | return treeAssembler.getOrDefault(l, Material.AIR.createBlockData()).getMaterial(); 104 | } 105 | 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/tree/fractal/TreeGeometry.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.tree.fractal; 2 | 3 | import org.bukkit.Location; 4 | import org.bukkit.Material; 5 | import org.bukkit.util.Vector; 6 | import org.polydev.gaea.math.ProbabilityCollection; 7 | 8 | public class TreeGeometry { 9 | private final FractalTree tree; 10 | 11 | public TreeGeometry(FractalTree tree) { 12 | this.tree = tree; 13 | } 14 | 15 | public static Vector getPerpendicular(Vector v) { 16 | return v.getZ() < v.getX() ? new Vector(v.getY(), - v.getX(), 0) : new Vector(0, - v.getZ(), v.getY()); 17 | } 18 | 19 | public FractalTree getTree() { 20 | return tree; 21 | } 22 | 23 | public void generateSphere(Location l, Material m, int radius, boolean overwrite) { 24 | generateSphere(l, new ProbabilityCollection().add(m, 1), radius, overwrite); 25 | } 26 | 27 | public void generateCylinder(Location l, Material m, int radius, int height, boolean overwrite) { 28 | generateCylinder(l, new ProbabilityCollection().add(m, 1), radius, height, overwrite); 29 | } 30 | 31 | public void generateSphere(Location l, ProbabilityCollection m, int radius, boolean overwrite) { 32 | for(int x = - radius; x <= radius; x++) { 33 | for(int y = - radius; y <= radius; y++) { 34 | for(int z = - radius; z <= radius; z++) { 35 | Vector position = l.toVector().clone().add(new Vector(x, y, z)); 36 | if(l.toVector().distance(position) <= radius + 0.5 && (overwrite || tree.getMaterial(position.toLocation(l.getWorld())).isAir())) 37 | tree.setBlock(position.toLocation(l.getWorld()), m.get()); 38 | } 39 | } 40 | } 41 | } 42 | 43 | public void generateSponge(Location l, ProbabilityCollection m, int radius, boolean overwrite, int sponginess) { 44 | for(int x = - radius; x <= radius; x++) { 45 | for(int y = - radius; y <= radius; y++) { 46 | for(int z = - radius; z <= radius; z++) { 47 | Vector position = l.toVector().clone().add(new Vector(x, y, z)); 48 | if(tree.getRandom().nextInt(100) < sponginess && l.toVector().distance(position) <= radius + 0.5 && (overwrite || tree.getMaterial(position.toLocation(l.getWorld())).isAir())) 49 | tree.setBlock(position.toLocation(l.getWorld()), m.get()); 50 | } 51 | } 52 | } 53 | } 54 | 55 | public void generateCylinder(Location l, ProbabilityCollection m, int radius, int height, boolean overwrite) { 56 | for(int x = - radius; x <= radius; x++) { 57 | for(int y = 0; y <= height; y++) { 58 | for(int z = - radius; z <= radius; z++) { 59 | Vector position = l.toVector().clone().add(new Vector(x, 0, z)); 60 | if(l.toVector().distance(position) <= radius + 0.5 && (overwrite || tree.getMaterial(position.toLocation(l.getWorld())).isAir())) 61 | tree.setBlock(position.toLocation(l.getWorld()), m.get()); 62 | } 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/tree/fractal/TreeGetter.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.tree.fractal; 2 | 3 | import org.bukkit.Location; 4 | 5 | import java.util.Random; 6 | 7 | public interface TreeGetter { 8 | FractalTree getTree(Location l, Random r); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/tree/fractal/trees/Cactus.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.tree.fractal.trees; 2 | 3 | import org.bukkit.Location; 4 | import org.bukkit.Material; 5 | import org.polydev.gaea.tree.fractal.FractalTree; 6 | 7 | import java.util.Random; 8 | 9 | public class Cactus extends FractalTree { 10 | /** 11 | * Instantiates a TreeGrower at an origin location. 12 | * 13 | * @param origin - The origin location. 14 | * @param random - The random object to use whilst generating the tree. 15 | */ 16 | public Cactus(Location origin, Random random) { 17 | super(origin, random); 18 | } 19 | 20 | /** 21 | * Grows the tree in memory. Intended to be invoked from an async thread. 22 | */ 23 | @Override 24 | public void grow() { 25 | int h = super.getRandom().nextInt(4) + 1; 26 | for(int i = 0; i < h; i++) setBlock(super.getOrigin().clone().add(0, i, 0), Material.CACTUS); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/tree/fractal/trees/IceSpike.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.tree.fractal.trees; 2 | 3 | import org.bukkit.Location; 4 | import org.bukkit.Material; 5 | import org.bukkit.util.Vector; 6 | import org.polydev.gaea.math.ProbabilityCollection; 7 | import org.polydev.gaea.tree.fractal.FractalTree; 8 | import org.polydev.gaea.tree.fractal.TreeGeometry; 9 | 10 | import java.util.Random; 11 | 12 | 13 | public class IceSpike extends FractalTree { 14 | private final TreeGeometry geo; 15 | private static final ProbabilityCollection ice = new ProbabilityCollection().add(Material.PACKED_ICE, 95).add(Material.BLUE_ICE, 5); 16 | 17 | /** 18 | * Instantiates a TreeGrower at an origin location. 19 | * 20 | * @param origin - The origin location. 21 | * @param random - The random object to use whilst generating the tree. 22 | */ 23 | public IceSpike(Location origin, Random random) { 24 | super(origin, random); 25 | geo = new TreeGeometry(this); 26 | } 27 | 28 | private double getOffset() { 29 | return (getRandom().nextDouble() - 0.5D); 30 | } 31 | 32 | /** 33 | * Grows the tree in memory. Intended to be invoked from an async thread. 34 | */ 35 | @Override 36 | public void grow() { 37 | Vector direction = new Vector(getOffset(), 0, getOffset()); 38 | Location l1 = super.getOrigin().clone(); 39 | int h = super.getRandom().nextInt(16) + 8; 40 | for(int i = 0; i < h; i++) { 41 | geo.generateSponge(l1.clone().add(0, i, 0).add(direction.clone().multiply(i)), ice, (int) ((1 - ((double) i / h)) * 2 + 1), true, 80); 42 | } 43 | for(int i = 0; i < h/3; i++) { 44 | setBlock(l1.clone().add(0, h + i, 0).add(direction.clone().multiply(h + i)), ice.get(super.getRandom())); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/tree/fractal/trees/OakTree.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.tree.fractal.trees; 2 | 3 | import net.jafama.FastMath; 4 | import org.bukkit.Location; 5 | import org.bukkit.Material; 6 | import org.bukkit.util.Vector; 7 | import org.polydev.gaea.tree.fractal.FractalTree; 8 | import org.polydev.gaea.tree.fractal.TreeGeometry; 9 | 10 | import java.util.Random; 11 | 12 | 13 | public class OakTree extends FractalTree { 14 | private final TreeGeometry geo; 15 | 16 | /** 17 | * Instantiates a TreeGrower at an origin location. 18 | * 19 | * @param origin - The origin location. 20 | * @param random - The random object to use whilst generating the tree. 21 | */ 22 | public OakTree(Location origin, Random random) { 23 | super(origin, random); 24 | geo = new TreeGeometry(this); 25 | } 26 | 27 | /** 28 | * Grows the tree in memory. Intended to be invoked from an async thread. 29 | */ 30 | @Override 31 | public void grow() { 32 | growBranch(super.getOrigin().clone(), new Vector(super.getRandom().nextInt(5) - 2, super.getRandom().nextInt(4) + 6, super.getRandom().nextInt(5) - 2), 2, 0); 33 | } 34 | 35 | private void growBranch(Location l1, Vector diff, double d1, int recursions) { 36 | if(recursions > 1) { 37 | geo.generateSphere(l1, Material.OAK_LEAVES, 1 + super.getRandom().nextInt(2) + (3 - recursions), false); 38 | if(recursions > 2) return; 39 | } 40 | if(diff.getY() < 0) diff.rotateAroundAxis(TreeGeometry.getPerpendicular(diff.clone()).normalize(), FastMath.PI); 41 | int d = (int) diff.length(); 42 | for(int i = 0; i < d; i++) { 43 | geo.generateSphere(l1.clone().add(diff.clone().multiply((double) i / d)), Material.OAK_WOOD, FastMath.max((int) d1, 0), true); 44 | } 45 | double runningAngle = (double) 45 / (recursions + 1); 46 | growBranch(l1.clone().add(diff), diff.clone().multiply(0.75).rotateAroundX(FastMath.toRadians(runningAngle + getNoise())).rotateAroundZ(FastMath.toRadians(getNoise())), 47 | d1 - 1, recursions + 1); 48 | growBranch(l1.clone().add(diff), diff.clone().multiply(0.75).rotateAroundX(FastMath.toRadians(- runningAngle + getNoise())).rotateAroundZ(FastMath.toRadians(getNoise())), 49 | d1 - 1, recursions + 1); 50 | growBranch(l1.clone().add(diff), diff.clone().multiply(0.75).rotateAroundZ(FastMath.toRadians(runningAngle + getNoise())).rotateAroundX(FastMath.toRadians(getNoise())), 51 | d1 - 1, recursions + 1); 52 | growBranch(l1.clone().add(diff), diff.clone().multiply(0.75).rotateAroundZ(FastMath.toRadians(- runningAngle + getNoise())).rotateAroundX(FastMath.toRadians(getNoise())), 53 | d1 - 1, recursions + 1); 54 | } 55 | 56 | private int getNoise() { 57 | return super.getRandom().nextInt(60) - 30; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/tree/fractal/trees/Rock.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.tree.fractal.trees; 2 | 3 | import org.bukkit.Location; 4 | import org.bukkit.Material; 5 | import org.polydev.gaea.math.FastNoiseLite; 6 | import org.polydev.gaea.math.ProbabilityCollection; 7 | import org.polydev.gaea.tree.fractal.FractalTree; 8 | import org.polydev.gaea.tree.fractal.TreeGeometry; 9 | 10 | import java.util.Random; 11 | 12 | 13 | public class Rock extends FractalTree { 14 | private final TreeGeometry geo; 15 | private final FastNoiseLite noise; 16 | private final FastNoiseLite rock; 17 | private static final ProbabilityCollection ice = new ProbabilityCollection().add(Material.PACKED_ICE, 95).add(Material.BLUE_ICE, 5); 18 | 19 | /** 20 | * Instantiates a TreeGrower at an origin location. 21 | * 22 | * @param origin - The origin location. 23 | * @param random - The random object to use whilst generating the tree. 24 | */ 25 | public Rock(Location origin, Random random) { 26 | super(origin, random); 27 | int seed = origin.hashCode(); 28 | this.noise = new FastNoiseLite(seed); 29 | this.rock = new FastNoiseLite(++seed); 30 | geo = new TreeGeometry(this); 31 | } 32 | 33 | /** 34 | * Grows the tree in memory. Intended to be invoked from an async thread. 35 | */ 36 | @Override 37 | public void grow() { 38 | Location l1 = super.getOrigin().clone(); 39 | int h = super.getRandom().nextInt(16) + 8; 40 | for(int i = 0; i < h; i++) { 41 | geo.generateSponge(l1.clone().add(0, i, 0), ice, (int) ((1-((double) i / h))*2+1), true, 70); 42 | } 43 | for(int i = 0; i < h/3; i++) { 44 | setBlock(l1.clone().add(0, h+i, 0), ice.get(super.getRandom())); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/tree/fractal/trees/ShatteredPillar.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.tree.fractal.trees; 2 | 3 | import org.bukkit.Location; 4 | import org.bukkit.Material; 5 | import org.bukkit.entity.EnderCrystal; 6 | import org.polydev.gaea.tree.fractal.FractalTree; 7 | 8 | import java.util.Random; 9 | 10 | public class ShatteredPillar extends FractalTree { 11 | /** 12 | * Instantiates a TreeGrower at an origin location. 13 | * 14 | * @param origin - The origin location. 15 | * @param random - The random object to use whilst generating the tree. 16 | */ 17 | public ShatteredPillar(Location origin, Random random) { 18 | super(origin, random); 19 | } 20 | 21 | /** 22 | * Grows the tree in memory. Intended to be invoked from an async thread. 23 | */ 24 | @Override 25 | public void grow() { 26 | int h = super.getRandom().nextInt(5) + 8; 27 | int max = h; 28 | for(int i = - h; i < h; i++) setBlock(super.getOrigin().clone().add(0, i, 0), Material.OBSIDIAN); 29 | h = h + (getRandom().nextBoolean() ? getRandom().nextInt(3) + 1 : - (getRandom().nextInt(3) + 1)); 30 | int[] crystalLoc = new int[] {0, 0}; 31 | if(h > max) { 32 | max = h; 33 | crystalLoc = new int[] {1, 0}; 34 | } 35 | for(int i = - h; i < h; i++) setBlock(super.getOrigin().clone().add(1, i, 0), Material.OBSIDIAN); 36 | h = h + (getRandom().nextBoolean() ? getRandom().nextInt(3) + 1 : - (getRandom().nextInt(3) + 1)); 37 | if(h > max) { 38 | max = h; 39 | crystalLoc = new int[] {0, 1}; 40 | } 41 | for(int i = - h; i < h; i++) setBlock(super.getOrigin().clone().add(0, i, 1), Material.OBSIDIAN); 42 | h = h + (getRandom().nextBoolean() ? getRandom().nextInt(3) + 1 : - (getRandom().nextInt(3) + 1)); 43 | if(h > max) { 44 | max = h; 45 | crystalLoc = new int[] {1, 1}; 46 | } 47 | for(int i = - h; i < h; i++) setBlock(super.getOrigin().clone().add(1, i, 1), Material.OBSIDIAN); 48 | if(getRandom().nextInt(100) < 25) 49 | spawnEntity(getOrigin().add(crystalLoc[0] + 0.5, max, crystalLoc[1] + 0.5), EnderCrystal.class, 50 | enderCrystal -> ((EnderCrystal) enderCrystal).setShowingBottom(false)); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/tree/fractal/trees/ShatteredTree.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.tree.fractal.trees; 2 | 3 | import net.jafama.FastMath; 4 | import org.bukkit.Location; 5 | import org.bukkit.Material; 6 | import org.bukkit.util.Vector; 7 | import org.polydev.gaea.math.ProbabilityCollection; 8 | import org.polydev.gaea.tree.fractal.FractalTree; 9 | import org.polydev.gaea.tree.fractal.TreeGeometry; 10 | 11 | import java.util.Random; 12 | 13 | public class ShatteredTree extends FractalTree { 14 | private final TreeGeometry geo; 15 | private final ProbabilityCollection bark = new ProbabilityCollection() 16 | .add(Material.OBSIDIAN, 1) 17 | .add(Material.BLACK_CONCRETE, 1); 18 | private final ProbabilityCollection leaves = new ProbabilityCollection() 19 | .add(Material.PURPLE_STAINED_GLASS, 1) 20 | .add(Material.MAGENTA_STAINED_GLASS, 1); 21 | 22 | /** 23 | * Instantiates a TreeGrower at an origin location. 24 | * 25 | * @param origin - The origin location. 26 | * @param random - The random object to use whilst generating the tree. 27 | */ 28 | public ShatteredTree(Location origin, Random random) { 29 | super(origin, random); 30 | geo = new TreeGeometry(this); 31 | } 32 | 33 | /** 34 | * Grows the tree in memory. Intended to be invoked from an async thread. 35 | */ 36 | @Override 37 | public void grow() { 38 | growBranch(super.getOrigin().clone(), new Vector(super.getRandom().nextInt(5) - 2, super.getRandom().nextInt(4) + 6, super.getRandom().nextInt(5) - 2), 1, 0); 39 | 40 | } 41 | 42 | private void growBranch(Location l1, Vector diff, double d1, int recursions) { 43 | if(recursions > 2) { 44 | geo.generateSphere(l1, leaves, 1 + super.getRandom().nextInt(2), false); 45 | return; 46 | } 47 | if(diff.getY() < 0) diff.rotateAroundAxis(TreeGeometry.getPerpendicular(diff.clone()).normalize(), FastMath.PI); 48 | int d = (int) diff.length(); 49 | for(int i = 0; i < d; i++) { 50 | geo.generateSphere(l1.clone().add(diff.clone().multiply((double) i / d)), bark, FastMath.max((int) d1, 0), true); 51 | } 52 | double runningAngle = (double) 45 / (recursions + 1); 53 | growBranch(l1.clone().add(diff), diff.clone().multiply(0.9).rotateAroundX(FastMath.toRadians(runningAngle + getNoise())).rotateAroundZ(FastMath.toRadians(getNoise())), 54 | d1 - 1, recursions + 1); 55 | growBranch(l1.clone().add(diff), diff.clone().multiply(0.9).rotateAroundX(FastMath.toRadians(- runningAngle + getNoise())).rotateAroundZ(FastMath.toRadians(getNoise())), 56 | d1 - 1, recursions + 1); 57 | growBranch(l1.clone().add(diff), diff.clone().multiply(0.9).rotateAroundZ(FastMath.toRadians(runningAngle + getNoise())).rotateAroundX(FastMath.toRadians(getNoise())), 58 | d1 - 1, recursions + 1); 59 | growBranch(l1.clone().add(diff), diff.clone().multiply(0.9).rotateAroundZ(FastMath.toRadians(- runningAngle + getNoise())).rotateAroundX(FastMath.toRadians(getNoise())), 60 | d1 - 1, recursions + 1); 61 | } 62 | 63 | private int getNoise() { 64 | return super.getRandom().nextInt(90) - 45; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/tree/fractal/trees/SmallShatteredPillar.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.tree.fractal.trees; 2 | 3 | import org.bukkit.Location; 4 | import org.bukkit.Material; 5 | import org.polydev.gaea.tree.fractal.FractalTree; 6 | 7 | import java.util.Random; 8 | 9 | public class SmallShatteredPillar extends FractalTree { 10 | /** 11 | * Instantiates a TreeGrower at an origin location. 12 | * 13 | * @param origin - The origin location. 14 | * @param random - The random object to use whilst generating the tree. 15 | */ 16 | public SmallShatteredPillar(Location origin, Random random) { 17 | super(origin, random); 18 | } 19 | 20 | /** 21 | * Grows the tree in memory. Intended to be invoked from an async thread. 22 | */ 23 | @Override 24 | public void grow() { 25 | int h = super.getRandom().nextInt(5) + 5; 26 | for(int i = - h; i < h; i++) setBlock(super.getOrigin().clone().add(0, i, 0), Material.OBSIDIAN); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/tree/fractal/trees/SmallShatteredTree.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.tree.fractal.trees; 2 | 3 | import net.jafama.FastMath; 4 | import org.bukkit.Location; 5 | import org.bukkit.Material; 6 | import org.bukkit.util.Vector; 7 | import org.polydev.gaea.math.ProbabilityCollection; 8 | import org.polydev.gaea.tree.fractal.FractalTree; 9 | import org.polydev.gaea.tree.fractal.TreeGeometry; 10 | 11 | import java.util.Random; 12 | 13 | public class SmallShatteredTree extends FractalTree { 14 | private final TreeGeometry geo; 15 | private final ProbabilityCollection bark = new ProbabilityCollection() 16 | .add(Material.OBSIDIAN, 1) 17 | .add(Material.BLACK_CONCRETE, 1); 18 | private final ProbabilityCollection leaves = new ProbabilityCollection() 19 | .add(Material.PURPLE_STAINED_GLASS, 1) 20 | .add(Material.MAGENTA_STAINED_GLASS, 1); 21 | 22 | /** 23 | * Instantiates a TreeGrower at an origin location. 24 | * 25 | * @param origin - The origin location. 26 | * @param random - The random object to use whilst generating the tree. 27 | */ 28 | public SmallShatteredTree(Location origin, Random random) { 29 | super(origin, random); 30 | geo = new TreeGeometry(this); 31 | } 32 | 33 | /** 34 | * Grows the tree in memory. Intended to be invoked from an async thread. 35 | */ 36 | @Override 37 | public void grow() { 38 | growBranch(super.getOrigin().clone(), new Vector(super.getRandom().nextInt(5) - 2, super.getRandom().nextInt(3) + 4, super.getRandom().nextInt(5) - 2), 1.5, 0); 39 | 40 | } 41 | 42 | private void growBranch(Location l1, Vector diff, double d1, int recursions) { 43 | if(recursions > 2) { 44 | geo.generateSphere(l1, leaves, 1 + super.getRandom().nextInt(2) + (3 - recursions), false); 45 | return; 46 | } 47 | if(diff.getY() < 0) diff.rotateAroundAxis(TreeGeometry.getPerpendicular(diff.clone()).normalize(), FastMath.PI); 48 | int d = (int) diff.length(); 49 | for(int i = 0; i < d; i++) { 50 | geo.generateSphere(l1.clone().add(diff.clone().multiply((double) i / d)), bark, FastMath.max((int) d1, 0), true); 51 | } 52 | double runningAngle = (double) 45 / (recursions + 1); 53 | growBranch(l1.clone().add(diff), diff.clone().multiply(0.7).rotateAroundX(FastMath.toRadians(runningAngle + getNoise())).rotateAroundZ(FastMath.toRadians(getNoise())), 54 | d1 - 1, recursions + 1); 55 | growBranch(l1.clone().add(diff), diff.clone().multiply(0.7).rotateAroundX(FastMath.toRadians(- runningAngle + getNoise())).rotateAroundZ(FastMath.toRadians(getNoise())), 56 | d1 - 1, recursions + 1); 57 | growBranch(l1.clone().add(diff), diff.clone().multiply(0.7).rotateAroundZ(FastMath.toRadians(runningAngle + getNoise())).rotateAroundX(FastMath.toRadians(getNoise())), 58 | d1 - 1, recursions + 1); 59 | growBranch(l1.clone().add(diff), diff.clone().multiply(0.7).rotateAroundZ(FastMath.toRadians(- runningAngle + getNoise())).rotateAroundX(FastMath.toRadians(getNoise())), 60 | d1 - 1, recursions + 1); 61 | } 62 | 63 | private int getNoise() { 64 | return super.getRandom().nextInt(90) - 45; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/tree/fractal/trees/SpruceTree.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.tree.fractal.trees; 2 | 3 | import net.jafama.FastMath; 4 | import org.bukkit.Location; 5 | import org.bukkit.Material; 6 | import org.bukkit.util.Vector; 7 | import org.polydev.gaea.tree.fractal.FractalTree; 8 | import org.polydev.gaea.tree.fractal.TreeGeometry; 9 | 10 | import java.util.Random; 11 | 12 | public class SpruceTree extends FractalTree { 13 | private final TreeGeometry geo; 14 | 15 | /** 16 | * Instantiates a TreeGrower at an origin location. 17 | * 18 | * @param origin - The origin location. 19 | * @param random - The random object to use whilst generating the tree. 20 | */ 21 | public SpruceTree(Location origin, Random random) { 22 | super(origin, random); 23 | geo = new TreeGeometry(this); 24 | } 25 | 26 | /** 27 | * Grows the tree in memory. Intended to be invoked from an async thread. 28 | */ 29 | @Override 30 | public void grow() { 31 | growTrunk(super.getOrigin().clone(), new Vector(0, 16 + super.getRandom().nextInt(5), 0)); 32 | } 33 | 34 | private void growTrunk(Location l1, Vector diff) { 35 | if(diff.getY() < 0) diff.rotateAroundAxis(TreeGeometry.getPerpendicular(diff.clone()).normalize(), FastMath.PI); 36 | int d = (int) diff.length(); 37 | int rad = 7; 38 | for(int i = 0; i < d; i++) { 39 | geo.generateSphere(l1.clone().add(diff.clone().multiply((double) i / d)), Material.SPRUCE_WOOD, (int) ((i > d * 0.65) ? 0.5 : 1.5), true); 40 | if(i > 3) 41 | geo.generateCylinder(l1.clone().add(diff.clone().multiply((double) i / d)), Material.SPRUCE_LEAVES, (int) (((6 - (i % 4))) * (1.25 - ((double) i / d))), 1, false); 42 | } 43 | setBlock(l1.clone().add(diff), Material.SPRUCE_LEAVES); 44 | setBlock(l1.clone().add(diff).add(0, 1, 0), Material.SPRUCE_LEAVES); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/util/FastRandom.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.util; 2 | 3 | import org.apache.commons.rng.core.source64.XoRoShiRo128PlusPlus; 4 | 5 | import java.util.Random; 6 | import java.util.SplittableRandom; 7 | 8 | public class FastRandom extends Random { 9 | 10 | private XoRoShiRo128PlusPlus random; 11 | 12 | public FastRandom() { 13 | super(); 14 | SplittableRandom randomseed = new SplittableRandom(); 15 | this.random = new XoRoShiRo128PlusPlus(randomseed.nextLong(), randomseed.nextLong()); 16 | } 17 | 18 | public FastRandom(long seed) { 19 | super(seed); 20 | SplittableRandom randomseed = new SplittableRandom(seed); 21 | this.random = new XoRoShiRo128PlusPlus(randomseed.nextLong(), randomseed.nextLong()); 22 | } 23 | 24 | @Override 25 | public boolean nextBoolean() { 26 | return random.nextBoolean(); 27 | } 28 | 29 | @Override 30 | public int nextInt() { 31 | return random.nextInt(); 32 | } 33 | 34 | @Override 35 | public float nextFloat() { 36 | return (float) random.nextDouble(); 37 | } 38 | 39 | @Override 40 | public double nextDouble() { 41 | return random.nextDouble(); 42 | } 43 | 44 | @Override 45 | public synchronized void setSeed(long seed) { 46 | SplittableRandom randomseed = new SplittableRandom(seed); 47 | this.random = new XoRoShiRo128PlusPlus(randomseed.nextLong(), randomseed.nextLong()); 48 | } 49 | 50 | @Override 51 | public void nextBytes(byte[] bytes) { 52 | random.nextBytes(bytes); 53 | } 54 | 55 | @Override 56 | public int nextInt(int bound) { 57 | return random.nextInt(bound); 58 | } 59 | 60 | @Override 61 | public long nextLong() { 62 | return random.nextLong(); 63 | } 64 | } -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/util/JarUtil.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.util; 2 | 3 | import org.polydev.gaea.Debug; 4 | 5 | import java.io.File; 6 | import java.io.FileOutputStream; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.util.Enumeration; 10 | import java.util.jar.JarEntry; 11 | import java.util.jar.JarFile; 12 | 13 | public class JarUtil { 14 | public static void copyResourcesToDirectory(JarFile fromJar, String sourceDir, String destDir) throws IOException { 15 | for(Enumeration entries = fromJar.entries(); entries.hasMoreElements(); ) { 16 | JarEntry entry = entries.nextElement(); 17 | if(entry.getName().startsWith(sourceDir + "/") && ! entry.isDirectory()) { 18 | File dest = new File(destDir + File.separator + entry.getName().substring(sourceDir.length() + 1)); 19 | if(dest.exists()) continue; 20 | File parent = dest.getParentFile(); 21 | if(parent != null) { 22 | parent.mkdirs(); 23 | } 24 | Debug.info("Output does not already exist. Creating... "); 25 | try(FileOutputStream out = new FileOutputStream(dest); InputStream in = fromJar.getInputStream(entry)) { 26 | byte[] buffer = new byte[(8192)]; 27 | 28 | int s; 29 | while((s = in.read(buffer)) > 0) { 30 | out.write(buffer, 0, s); 31 | } 32 | } catch(IOException e) { 33 | throw new IOException("Could not copy asset from jar file", e); 34 | } 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/util/SerializationUtil.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.util; 2 | 3 | import org.polydev.gaea.serial.MovedObjectInputStream; 4 | 5 | import java.io.*; 6 | 7 | public class SerializationUtil { 8 | public static Object fromFile(File f) throws IOException, ClassNotFoundException { 9 | ObjectInputStream ois = new MovedObjectInputStream(new FileInputStream(f), "com.dfsek.betterend.gaea", "org.polydev.gaea"); // Backwards compat with old BetterEnd shade location 10 | Object o = ois.readObject(); 11 | ois.close(); 12 | return o; 13 | } 14 | 15 | public static void toFile(Serializable o, File f) throws IOException { 16 | ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(f)); 17 | oos.writeObject(o); 18 | oos.close(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/util/WorldUtil.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.util; 2 | 3 | import net.jafama.FastMath; 4 | import org.bukkit.Chunk; 5 | import org.bukkit.Location; 6 | import org.bukkit.Material; 7 | 8 | import java.util.List; 9 | 10 | public class WorldUtil { 11 | public static int getHighestValidSpawnAt(Chunk chunk, int x, int z) { 12 | int y; 13 | for(y = chunk.getWorld().getMaxHeight() - 1; (chunk.getBlock(x, y, z).getType() != Material.GRASS_BLOCK 14 | && chunk.getBlock(x, y, z).getType() != Material.GRAVEL 15 | && chunk.getBlock(x, y, z).getType() != Material.PODZOL 16 | && chunk.getBlock(x, y, z).getType() != Material.END_STONE 17 | && chunk.getBlock(x, y, z).getType() != Material.DIRT 18 | && chunk.getBlock(x, y, z).getType() != Material.STONE 19 | && chunk.getBlock(x, y, z).getType() != Material.SAND 20 | && chunk.getBlock(x, y, z).getType() != Material.RED_SAND 21 | && chunk.getBlock(x, y, z).getType() != Material.COARSE_DIRT) && y > 0; y--) 22 | ; 23 | return y; 24 | } 25 | 26 | public static int getHighestBlockAt(Chunk chunk, int x, int z) { 27 | int y; 28 | for(y = chunk.getWorld().getMaxHeight() - 1; (chunk.getBlock(x, y, z).getType().isAir()) && y > 0; y--) ; 29 | return y; 30 | } 31 | 32 | public static List getLocationListBetween(Location loc1, Location loc2) { 33 | int lowX = FastMath.min(loc1.getBlockX(), loc2.getBlockX()); 34 | int lowY = FastMath.min(loc1.getBlockY(), loc2.getBlockY()); 35 | int lowZ = FastMath.min(loc1.getBlockZ(), loc2.getBlockZ()); 36 | List locs = new GlueList<>(); 37 | for(int x = 0; x <= FastMath.abs(loc1.getBlockX() - loc2.getBlockX()); x++) { 38 | for(int y = 0; y <= FastMath.abs(loc1.getBlockY() - loc2.getBlockY()); y++) { 39 | for(int z = 0; z <= FastMath.abs(loc1.getBlockZ() - loc2.getBlockZ()); z++) { 40 | locs.add(new Location(loc1.getWorld(), (double) lowX + x, (double) lowY + y, (double) lowZ + z)); 41 | } 42 | } 43 | } 44 | return locs; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/world/Flora.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.world; 2 | 3 | import org.bukkit.Chunk; 4 | import org.bukkit.Location; 5 | import org.bukkit.block.Block; 6 | import org.polydev.gaea.math.Range; 7 | 8 | import java.util.List; 9 | 10 | public interface Flora { 11 | List getValidSpawnsAt(Chunk chunk, int x, int z, Range check); 12 | 13 | boolean plant(Location l); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/world/FloraType.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.world; 2 | 3 | import com.google.common.collect.Sets; 4 | import org.bukkit.Bukkit; 5 | import org.bukkit.Chunk; 6 | import org.bukkit.Location; 7 | import org.bukkit.Material; 8 | import org.bukkit.block.Block; 9 | import org.bukkit.block.BlockFace; 10 | import org.bukkit.block.data.BlockData; 11 | import org.polydev.gaea.math.Range; 12 | import org.polydev.gaea.util.GlueList; 13 | 14 | import java.util.Arrays; 15 | import java.util.List; 16 | import java.util.Set; 17 | 18 | public enum FloraType implements Flora { 19 | TALL_GRASS(Sets.newHashSet(Material.GRASS_BLOCK, Material.PODZOL), Bukkit.createBlockData("minecraft:tall_grass[half=lower]"), Bukkit.createBlockData("minecraft:tall_grass[half=upper]")), 20 | TALL_FERN(Sets.newHashSet(Material.GRASS_BLOCK, Material.PODZOL), Bukkit.createBlockData("minecraft:large_fern[half=lower]"), Bukkit.createBlockData("minecraft:large_fern[half=upper]")), 21 | SUNFLOWER(Sets.newHashSet(Material.GRASS_BLOCK, Material.PODZOL), Bukkit.createBlockData("minecraft:sunflower[half=lower]"), Bukkit.createBlockData("minecraft:sunflower[half=upper]")), 22 | ROSE_BUSH(Sets.newHashSet(Material.GRASS_BLOCK, Material.PODZOL), Bukkit.createBlockData("minecraft:rose_bush[half=lower]"), Bukkit.createBlockData("minecraft:rose_bush[half=upper]")), 23 | LILAC(Sets.newHashSet(Material.GRASS_BLOCK, Material.PODZOL), Bukkit.createBlockData("minecraft:lilac[half=lower]"), Bukkit.createBlockData("minecraft:lilac[half=upper]")), 24 | PEONY(Sets.newHashSet(Material.GRASS_BLOCK, Material.PODZOL), Bukkit.createBlockData("minecraft:peony[half=lower]"), Bukkit.createBlockData("minecraft:peony[half=upper]")), 25 | GRASS(Sets.newHashSet(Material.GRASS_BLOCK, Material.PODZOL), Bukkit.createBlockData("minecraft:grass")), 26 | FERN(Sets.newHashSet(Material.GRASS_BLOCK, Material.PODZOL), Bukkit.createBlockData("minecraft:fern")), 27 | AZURE_BLUET(Sets.newHashSet(Material.GRASS_BLOCK, Material.PODZOL), Bukkit.createBlockData("minecraft:azure_bluet")), 28 | LILY_OF_THE_VALLEY(Sets.newHashSet(Material.GRASS_BLOCK, Material.PODZOL), Bukkit.createBlockData("minecraft:lily_of_the_valley")), 29 | BLUE_ORCHID(Sets.newHashSet(Material.GRASS_BLOCK, Material.PODZOL), Bukkit.createBlockData("minecraft:blue_orchid")), 30 | POPPY(Sets.newHashSet(Material.GRASS_BLOCK, Material.PODZOL), Bukkit.createBlockData("minecraft:poppy")), 31 | DANDELION(Sets.newHashSet(Material.GRASS_BLOCK, Material.PODZOL), Bukkit.createBlockData("minecraft:dandelion")), 32 | WITHER_ROSE(Sets.newHashSet(Material.GRASS_BLOCK, Material.PODZOL), Bukkit.createBlockData("minecraft:wither_rose")), 33 | DEAD_BUSH(Sets.newHashSet(Material.GRASS_BLOCK, Material.PODZOL, Material.SAND, Material.RED_SAND, 34 | Material.TERRACOTTA, Material.BLACK_TERRACOTTA, Material.BLUE_TERRACOTTA, Material.BROWN_TERRACOTTA, Material.CYAN_TERRACOTTA, Material.GRAY_TERRACOTTA, 35 | Material.GREEN_TERRACOTTA, Material.LIGHT_BLUE_TERRACOTTA, Material.LIGHT_GRAY_TERRACOTTA, Material.LIME_TERRACOTTA, Material.MAGENTA_TERRACOTTA, 36 | Material.ORANGE_TERRACOTTA, Material.PINK_TERRACOTTA, Material.PURPLE_TERRACOTTA, Material.RED_TERRACOTTA, Material.RED_TERRACOTTA, Material.WHITE_TERRACOTTA, 37 | Material.YELLOW_TERRACOTTA), Bukkit.createBlockData("minecraft:dead_bush")), 38 | RED_TULIP(Sets.newHashSet(Material.GRASS_BLOCK, Material.PODZOL), Bukkit.createBlockData("minecraft:red_tulip")), 39 | ORANGE_TULIP(Sets.newHashSet(Material.GRASS_BLOCK, Material.PODZOL), Bukkit.createBlockData("minecraft:orange_tulip")), 40 | WHITE_TULIP(Sets.newHashSet(Material.GRASS_BLOCK, Material.PODZOL), Bukkit.createBlockData("minecraft:white_tulip")), 41 | PINK_TULIP(Sets.newHashSet(Material.GRASS_BLOCK, Material.PODZOL), Bukkit.createBlockData("minecraft:pink_tulip")), 42 | OXEYE_DAISY(Sets.newHashSet(Material.GRASS_BLOCK, Material.PODZOL), Bukkit.createBlockData("minecraft:oxeye_daisy")), 43 | ALLIUM(Sets.newHashSet(Material.GRASS_BLOCK, Material.PODZOL), Bukkit.createBlockData("minecraft:allium")), 44 | CORNFLOWER(Sets.newHashSet(Material.GRASS_BLOCK, Material.PODZOL), Bukkit.createBlockData("minecraft:cornflower")), 45 | LILY_PAD(Sets.newHashSet(Material.WATER), Bukkit.createBlockData("minecraft:lily_pad")), 46 | RED_MUSHROOM(Sets.newHashSet(Material.GRASS_BLOCK, Material.PODZOL, Material.DIRT, Material.STONE, Material.NETHERRACK, Material.MYCELIUM), Bukkit.createBlockData("minecraft:red_mushroom")), 47 | BROWN_MUSHROOM(Sets.newHashSet(Material.GRASS_BLOCK, Material.PODZOL, Material.DIRT, Material.STONE, Material.NETHERRACK, Material.MYCELIUM), Bukkit.createBlockData("minecraft:brown_mushroom")), 48 | ; 49 | 50 | private final List data = new GlueList<>(); 51 | 52 | private final Set spawns; 53 | 54 | FloraType(Set validSpawns, BlockData... type) { 55 | data.addAll(Arrays.asList(type)); 56 | this.spawns = validSpawns; 57 | } 58 | 59 | @Override 60 | public List getValidSpawnsAt(Chunk chunk, int x, int z, Range check) { 61 | List blocks = new GlueList<>(); 62 | for(int y : check) { 63 | Block block = chunk.getBlock(x, y, z); 64 | if(spawns.contains(block.getType()) && valid(block)) { 65 | blocks.add(chunk.getBlock(x, y, z)); 66 | } 67 | } 68 | return blocks; 69 | } 70 | 71 | private boolean valid(Block block) { 72 | for(int i = 1; i < data.size() + 1; i++) { 73 | block = block.getRelative(BlockFace.UP); 74 | if(!block.isEmpty()) return false; 75 | } 76 | return true; 77 | } 78 | 79 | @Override 80 | public boolean plant(Location l) { 81 | for(int i = 1; i < data.size() + 1; i++) { 82 | l.clone().add(0, i, 0).getBlock().setBlockData(data.get(i - 1), false); 83 | } 84 | return true; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/world/Ore.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.world; 2 | 3 | import org.bukkit.block.data.BlockData; 4 | 5 | public class Ore { 6 | private final int contChance; 7 | private final BlockData oreMaterial; 8 | 9 | public Ore(BlockData oreMaterial, int contChance) { 10 | this.contChance = contChance; 11 | this.oreMaterial = oreMaterial; 12 | } 13 | 14 | public int getContChance() { 15 | return contChance; 16 | } 17 | 18 | public BlockData getType() { 19 | return oreMaterial; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/world/carving/Carver.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.world.carving; 2 | 3 | import net.jafama.FastMath; 4 | import org.bukkit.World; 5 | import org.bukkit.util.Vector; 6 | import org.polydev.gaea.math.MathUtil; 7 | import org.polydev.gaea.util.FastRandom; 8 | 9 | import java.util.Random; 10 | import java.util.function.BiConsumer; 11 | 12 | public abstract class Carver { 13 | private final int minY; 14 | private final int maxY; 15 | private final double sixtyFourSq = FastMath.pow(64, 2); 16 | private int carvingRadius = 4; 17 | 18 | public Carver(int minY, int maxY) { 19 | this.minY = minY; 20 | this.maxY = maxY; 21 | } 22 | 23 | public void carve(int chunkX, int chunkZ, World w, BiConsumer consumer) { 24 | for(int x = chunkX - carvingRadius; x <= chunkX + carvingRadius; x++) { 25 | for(int z = chunkZ - carvingRadius; z <= chunkZ + carvingRadius; z++) { 26 | if(isChunkCarved(w, x, z, new FastRandom(MathUtil.hashToLong(this.getClass().getName() + "_" + x + "&" + z)))) { 27 | long seed = MathUtil.getCarverChunkSeed(x, z, w.getSeed()); 28 | Random r = new FastRandom(seed); 29 | Worm carving = getWorm(seed, new Vector((x << 4) + r.nextInt(16), r.nextInt(maxY - minY + 1) + minY, (z << 4) + r.nextInt(16))); 30 | Vector origin = carving.getOrigin(); 31 | for(int i = 0; i < carving.getLength(); i++) { 32 | carving.step(); 33 | if(carving.getRunning().clone().setY(0).distanceSquared(origin.clone().setY(0)) > sixtyFourSq) 34 | break; 35 | if(FastMath.floorDiv(origin.getBlockX(), 16) != chunkX && FastMath.floorDiv(origin.getBlockZ(), 16) != chunkZ) 36 | continue; 37 | carving.getPoint().carve(chunkX, chunkZ, consumer); 38 | } 39 | } 40 | } 41 | } 42 | } 43 | 44 | public int getCarvingRadius() { 45 | return carvingRadius; 46 | } 47 | 48 | public void setCarvingRadius(int carvingRadius) { 49 | this.carvingRadius = carvingRadius; 50 | } 51 | 52 | public abstract Worm getWorm(long seed, Vector l); 53 | 54 | public abstract boolean isChunkCarved(World w, int chunkX, int chunkZ, Random r); 55 | 56 | public enum CarvingType { 57 | CENTER, WALL, TOP, BOTTOM 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/world/carving/Worm.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.world.carving; 2 | 3 | import net.jafama.FastMath; 4 | import org.bukkit.util.Vector; 5 | 6 | import java.util.Random; 7 | import java.util.function.BiConsumer; 8 | 9 | public abstract class Worm { 10 | private final Random r; 11 | private final Vector origin; 12 | private final Vector running; 13 | private final int length; 14 | private int topCut = 0; 15 | private int bottomCut = 0; 16 | private int[] radius = new int[] {0, 0, 0}; 17 | 18 | public Worm(int length, Random r, Vector origin) { 19 | this.r = r; 20 | this.length = length; 21 | this.origin = origin; 22 | this.running = origin; 23 | } 24 | 25 | public void setBottomCut(int bottomCut) { 26 | this.bottomCut = bottomCut; 27 | } 28 | 29 | public void setTopCut(int topCut) { 30 | this.topCut = topCut; 31 | } 32 | 33 | public Vector getOrigin() { 34 | return origin; 35 | } 36 | 37 | public int getLength() { 38 | return length; 39 | } 40 | 41 | public Vector getRunning() { 42 | return running; 43 | } 44 | 45 | public WormPoint getPoint() { 46 | return new WormPoint(running, radius, topCut, bottomCut); 47 | } 48 | 49 | public int[] getRadius() { 50 | return radius; 51 | } 52 | 53 | public void setRadius(int[] radius) { 54 | this.radius = radius; 55 | } 56 | 57 | public Random getRandom() { 58 | return r; 59 | } 60 | 61 | public abstract void step(); 62 | 63 | public static class WormPoint { 64 | private final Vector origin; 65 | private final int topCut; 66 | private final int bottomCut; 67 | private final int[] rad; 68 | 69 | public WormPoint(Vector origin, int[] rad, int topCut, int bottomCut) { 70 | this.origin = origin; 71 | this.rad = rad; 72 | this.topCut = topCut; 73 | this.bottomCut = bottomCut; 74 | } 75 | 76 | private static double ellipseEquation(int x, int y, int z, double xr, double yr, double zr) { 77 | return (FastMath.pow2(x) / FastMath.pow2(xr + 0.5D)) + (FastMath.pow2(y) / FastMath.pow2(yr + 0.5D)) + (FastMath.pow2(z) / FastMath.pow2(zr + 0.5D)); 78 | } 79 | 80 | public Vector getOrigin() { 81 | return origin; 82 | } 83 | 84 | public int getRadius(int index) { 85 | return rad[index]; 86 | } 87 | 88 | public void carve(int chunkX, int chunkZ, BiConsumer consumer) { 89 | int xRad = getRadius(0); 90 | int yRad = getRadius(1); 91 | int zRad = getRadius(2); 92 | int originX = (chunkX << 4); 93 | int originZ = (chunkZ << 4); 94 | for(int x = -xRad - 1; x <= xRad + 1; x++) { 95 | if(!(FastMath.floorDiv(origin.getBlockX() + x, 16) == chunkX)) continue; 96 | for(int z = -zRad - 1; z <= zRad + 1; z++) { 97 | if(!(FastMath.floorDiv(origin.getBlockZ() + z, 16) == chunkZ)) continue; 98 | for(int y = -yRad - 1; y <= yRad + 1; y++) { 99 | Vector position = origin.clone().add(new Vector(x, y, z)); 100 | if(position.getY() < 0 || position.getY() > 255) continue; 101 | double eq = ellipseEquation(x, y, z, xRad, yRad, zRad); 102 | if(eq <= 1 && 103 | y >= -yRad - 1 + bottomCut && y <= yRad + 1 - topCut) { 104 | consumer.accept(new Vector(position.getBlockX() - originX, position.getBlockY(), position.getBlockZ() - originZ), Carver.CarvingType.CENTER); 105 | } else if(eq <= 1.5) { 106 | Carver.CarvingType type = Carver.CarvingType.WALL; 107 | if(y <= -yRad - 1 + bottomCut) { 108 | type = Carver.CarvingType.BOTTOM; 109 | } else if(y >= yRad + 1 - topCut) { 110 | type = Carver.CarvingType.TOP; 111 | } 112 | consumer.accept(new Vector(position.getBlockX() - originX, position.getBlockY(), position.getBlockZ() - originZ), type); 113 | } 114 | } 115 | } 116 | } 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/world/palette/Palette.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.world.palette; 2 | 3 | import org.polydev.gaea.math.FastNoiseLite; 4 | import org.polydev.gaea.math.ProbabilityCollection; 5 | import org.polydev.gaea.util.GlueList; 6 | 7 | import java.util.List; 8 | import java.util.Random; 9 | 10 | /** 11 | * A class representation of a "slice" of the world. 12 | * Used to get a section of blocks, based on the depth at which they are found. 13 | */ 14 | public abstract class Palette { 15 | private final List> pallet = new GlueList<>(); 16 | 17 | /** 18 | * Constructs a blank palette. 19 | */ 20 | public Palette() { 21 | 22 | } 23 | 24 | /** 25 | * Adds a material to the palette, for a number of layers. 26 | * 27 | * @param m - The material to add to the palette. 28 | * @param layers - The number of layers the material occupies. 29 | * @return - BlockPalette instance for chaining. 30 | */ 31 | public Palette add(E m, int layers) { 32 | for(int i = 0; i < layers; i++) { 33 | pallet.add(new PaletteLayer<>(m)); 34 | } 35 | return this; 36 | } 37 | 38 | /** 39 | * Adds a ProbabilityCollection to the palette, for a number of layers. 40 | * 41 | * @param m - The ProbabilityCollection to add to the palette. 42 | * @param layers - The number of layers the material occupies. 43 | * @return - BlockPalette instance for chaining. 44 | */ 45 | public Palette add(ProbabilityCollection m, int layers) { 46 | for(int i = 0; i < layers; i++) { 47 | pallet.add(new PaletteLayer<>(m)); 48 | } 49 | return this; 50 | } 51 | 52 | /** 53 | * Fetches a material from the palette, at a given layer. 54 | * 55 | * @param layer - The layer at which to fetch the material. 56 | * @return BlockData - The material fetched. 57 | */ 58 | public abstract E get(int layer, int x, int z); 59 | 60 | 61 | public int getSize() { 62 | return pallet.size(); 63 | } 64 | 65 | public List> getLayers() { 66 | return pallet; 67 | } 68 | 69 | /** 70 | * Class representation of a layer of a BlockPalette. 71 | */ 72 | public static class PaletteLayer { 73 | private final boolean col; // Is layer using a collection? 74 | private ProbabilityCollection collection; 75 | private E m; 76 | 77 | /** 78 | * Constructs a PaletteLayer with a ProbabilityCollection of materials and a number of layers. 79 | * 80 | * @param type - The collection of materials to choose from. 81 | */ 82 | public PaletteLayer(ProbabilityCollection type) { 83 | this.col = true; 84 | this.collection = type; 85 | } 86 | 87 | /** 88 | * Constructs a PaletteLayer with a single Material and a number of layers. 89 | * 90 | * @param type - The material to use. 91 | */ 92 | public PaletteLayer(E type) { 93 | this.col = false; 94 | this.m = type; 95 | } 96 | 97 | /** 98 | * Gets a material from the layer. 99 | * 100 | * @return Material - the material.. 101 | */ 102 | public E get(Random random) { 103 | if(col) return this.collection.get(random); 104 | return m; 105 | } 106 | 107 | public E get(FastNoiseLite random, int x, int z) { 108 | if(col) return this.collection.get(random, x, z); 109 | return m; 110 | } 111 | 112 | public ProbabilityCollection getCollection() { 113 | return collection; 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/world/palette/RandomPalette.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.world.palette; 2 | 3 | import java.util.List; 4 | import java.util.Random; 5 | 6 | public class RandomPalette extends Palette { 7 | private final Random r; 8 | 9 | public RandomPalette(Random r) { 10 | this.r = r; 11 | } 12 | 13 | @Override 14 | public E get(int layer, int x, int z) { 15 | if(layer > this.getSize()) return this.getLayers().get(this.getLayers().size() - 1).get(r); 16 | List> pl = getLayers(); 17 | if(layer >= pl.size()) return pl.get(pl.size() - 1).get(r); 18 | return pl.get(layer).get(r); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/polydev/gaea/world/palette/SimplexPalette.java: -------------------------------------------------------------------------------- 1 | package org.polydev.gaea.world.palette; 2 | 3 | import org.polydev.gaea.math.FastNoiseLite; 4 | 5 | import java.util.List; 6 | 7 | public class SimplexPalette extends Palette { 8 | private final FastNoiseLite r; 9 | 10 | public SimplexPalette(FastNoiseLite r) { 11 | this.r = r; 12 | } 13 | 14 | @Override 15 | public E get(int layer, int x, int z) { 16 | if(layer > this.getSize()) return this.getLayers().get(this.getLayers().size() - 1).get(r, x, z); 17 | List> pl = getLayers(); 18 | if(layer >= pl.size()) return pl.get(pl.size() - 1).get(r, x, z); 19 | return pl.get(layer).get(r, x, z); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/config.yml: -------------------------------------------------------------------------------- 1 | debug: false -------------------------------------------------------------------------------- /src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: Gaea 2 | api-version: 1.16 3 | author: dfsek 4 | main: org.polydev.gaea.Gaea 5 | version: 1.15.0 6 | load: STARTUP -------------------------------------------------------------------------------- /src/test/java/CacheTest.java: -------------------------------------------------------------------------------- 1 | import org.bukkit.Material; 2 | import org.junit.jupiter.api.Test; 3 | import org.polydev.gaea.math.ProbabilityCollection; 4 | import org.polydev.gaea.util.FastRandom; 5 | import org.polydev.gaea.util.GlueList; 6 | 7 | import java.util.List; 8 | 9 | public class CacheTest { 10 | 11 | @Test 12 | public void regular() { 13 | ProbabilityCollection collection = new ProbabilityCollection<>(); 14 | collection.add(Material.DIRT, 100); 15 | collection.add(Material.GRASS_BLOCK, 127); 16 | collection.add(Material.GRASS_PATH, 101); 17 | 18 | FastRandom random = new FastRandom(123); 19 | 20 | long s = System.nanoTime(); 21 | for(int i = 0; i < 10000000; i++) { 22 | collection.get(random); 23 | } 24 | double t = (double) (System.nanoTime() - s); 25 | System.out.println(t / 1000000 + "ms, " + t / 10000000 + "ns per"); 26 | 27 | List longList = new GlueList<>(); 28 | for(int i = 0; i < 10000; i++) { 29 | long tm = System.nanoTime(); 30 | collection.get(random); 31 | long diff = System.nanoTime() - tm; 32 | longList.add(diff); 33 | } 34 | long sum = 0; 35 | for(long l : longList) sum += l; 36 | System.out.println(sum / longList.size() + "ns per get"); 37 | } 38 | 39 | @Test 40 | public void fast() { 41 | ProbabilityCollection collection = new ProbabilityCollection<>(); 42 | collection.add(Material.DIRT, 100); 43 | collection.add(Material.GRASS_BLOCK, 127); 44 | collection.add(Material.GRASS_PATH, 101); 45 | 46 | FastRandom random = new FastRandom(123); 47 | 48 | long s = System.nanoTime(); 49 | for(int i = 0; i < 10000000; i++) { 50 | collection.get(random); 51 | } 52 | double t = (double) (System.nanoTime() - s); 53 | System.out.println(t / 1000000 + "ms, " + t / 10000000 + "ns per"); 54 | 55 | List longList = new GlueList<>(); 56 | for(int i = 0; i < 10000; i++) { 57 | long tm = System.nanoTime(); 58 | collection.get(random); 59 | long diff = System.nanoTime() - tm; 60 | longList.add(diff); 61 | } 62 | long sum = 0; 63 | for(long l : longList) sum += l; 64 | System.out.println(sum / longList.size() + "ns per get"); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/CellularNoiseLookupTest.java: -------------------------------------------------------------------------------- 1 | import org.polydev.gaea.biome.NormalizationUtil; 2 | import org.polydev.gaea.math.FastNoiseLite; 3 | 4 | import javax.swing.*; 5 | import java.awt.*; 6 | 7 | public class CellularNoiseLookupTest extends JPanel { 8 | public CellularNoiseLookupTest() { 9 | } 10 | 11 | public static void main(String[] args) { 12 | 13 | 14 | JFrame frame = new JFrame("Noise Test"); 15 | 16 | frame.setSize(2048, 2048); 17 | 18 | frame.add(new CellularNoiseLookupTest()); 19 | System.out.println("done"); 20 | frame.setResizable(false); 21 | frame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); 22 | frame.setVisible(true); 23 | } 24 | 25 | @Override 26 | public void paint(Graphics g) { 27 | super.paint(g); 28 | FastNoiseLite noiseLite = new FastNoiseLite(); 29 | 30 | FastNoiseLite lookup = new FastNoiseLite(); 31 | lookup.setCellularNoiseLookup(lookup); 32 | lookup.setNoiseType(FastNoiseLite.NoiseType.Cellular); 33 | lookup.setCellularReturnType(FastNoiseLite.CellularReturnType.NoiseLookup); 34 | lookup.setFrequency(0.002); 35 | 36 | FastNoiseLite simplexLookup = new FastNoiseLite(); 37 | simplexLookup.setFrequency(0.001); 38 | 39 | lookup.setCellularNoiseLookup(simplexLookup); 40 | 41 | 42 | noiseLite.setCellularNoiseLookup(lookup); 43 | noiseLite.setNoiseType(FastNoiseLite.NoiseType.Cellular); 44 | noiseLite.setCellularReturnType(FastNoiseLite.CellularReturnType.NoiseLookup); 45 | noiseLite.setFrequency(0.01); 46 | for(int x = 0; x < 2048; x++) { 47 | for(int y = 0; y < 2048; y++) { 48 | int noiseR = NormalizationUtil.normalize(noiseLite.getNoise(x, y), 126, 1); 49 | int noiseG = NormalizationUtil.normalize(noiseLite.getCellularNoiseLookup().getNoise(x, y), 127, 1); 50 | g.setColor(new Color(noiseR + noiseG, noiseR + noiseG, noiseR + noiseG)); 51 | g.drawLine(x, y, x, y); 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/Interp3Test.java: -------------------------------------------------------------------------------- 1 | import org.junit.jupiter.api.Test; 2 | import org.polydev.gaea.math.Interpolator; 3 | import org.polydev.gaea.math.Interpolator3; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | 7 | public class Interp3Test { 8 | @Test 9 | public void interp3() { 10 | Interpolator3 i = new Interpolator3(1, 1, 1, 0, 0, 0, 0, 0, Interpolator.Type.NEAREST_NEIGHBOR); 11 | for(int y = 0; y < 8; y++) { 12 | for(int x = 0; x < 8; x++) { 13 | for(int z = 0; z < 8; z++) { 14 | System.out.print(i.trilerp(x / 6D, y / 6D, z / 6D) + " "); 15 | } 16 | System.out.println(); 17 | } 18 | System.out.println("\n\n"); 19 | } 20 | } 21 | @Test 22 | public void interpNN() { 23 | assertEquals(0.8, Interpolator.lerp(1, 0.8, 1.4, Interpolator.Type.NEAREST_NEIGHBOR)); 24 | assertEquals(1.4, Interpolator.lerp(1.3, 0.8, 1.4, Interpolator.Type.NEAREST_NEIGHBOR)); 25 | } 26 | @Test 27 | public void interpLN() { 28 | assertEquals(1, Interpolator.lerp(0.5, 0.8, 1.2, Interpolator.Type.LINEAR)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/PaletteTest.java: -------------------------------------------------------------------------------- 1 | import org.bukkit.Material; 2 | import org.junit.jupiter.api.Test; 3 | import org.polydev.gaea.math.ProbabilityCollection; 4 | import org.polydev.gaea.util.FastRandom; 5 | import org.polydev.gaea.world.palette.Palette; 6 | import org.polydev.gaea.world.palette.RandomPalette; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.Random; 11 | 12 | import static org.junit.jupiter.api.Assertions.assertEquals; 13 | 14 | public class PaletteTest { 15 | @Test 16 | public void getBlocks() { 17 | Palette palette = new RandomPalette<>(new FastRandom(2403)); 18 | for(int i = 0; i < 100; i++) { 19 | palette.add(i % 2 == 0 ? Material.DIRT : Material.STONE, 1); 20 | } 21 | for(int i = 0; i < 100; i++) { 22 | assertEquals(palette.get(i, 0, 0), i % 2 == 0 ? Material.DIRT : Material.STONE); 23 | } 24 | assertEquals(palette.get(10000, 0, 0), Material.STONE); 25 | } 26 | 27 | @Test 28 | public void getProbabilityBlocks() { 29 | 30 | } 31 | 32 | @Test 33 | public void main() { 34 | long l = System.nanoTime(); 35 | Random r = new FastRandom(); 36 | //testing time taken to instantiate/fill palette. Realistic test. 37 | Palette p = new RandomPalette<>(r); 38 | System.out.println((System.nanoTime() - l) / 1000 + "us elapsed (Instantiation)"); 39 | l = System.nanoTime(); 40 | p.add(Material.GRASS_BLOCK, 1); 41 | System.out.println((System.nanoTime() - l) / 1000000 + "ms elapsed (Fill 1)"); 42 | l = System.nanoTime(); 43 | p.add(Material.DIRT, 12); 44 | System.out.println((System.nanoTime() - l) / 1000000 + "ms elapsed (Fill 2)"); 45 | l = System.nanoTime(); 46 | p.add(new ProbabilityCollection().add(Material.STONE, 1).add(Material.DIRT, 1), 20); 47 | System.out.println((System.nanoTime() - l) / 1000000 + "ms elapsed (Fill 3)"); 48 | l = System.nanoTime(); 49 | p.add(Material.STONE, 30); 50 | System.out.println((System.nanoTime() - l) / 1000000 + "ms elapsed (Fill 4)"); 51 | l = System.nanoTime(); 52 | 53 | //testing time taken to get the top layer of materials Realistic test, however, much time is taken by System.out. 54 | List m = new ArrayList<>(); 55 | for(int i = 0; i < 10; i++) { 56 | long l2 = System.nanoTime(); 57 | m.add(p.get(i, 0, 0)); 58 | System.out.println(p.get(i, 0, 0) + " retrieved in " + (System.nanoTime() - l2) / 1000 + "us"); 59 | } 60 | System.out.println((double) (System.nanoTime() - l) / 1000000 + "ms elapsed (Getters, raw x10), got " + m.size() + " values"); 61 | 62 | //testing time taken to get 100k materials. Unrealistic stress test. 63 | for(int i = 0; i < 1000000; i++) { 64 | p.get(i, 0, 0); 65 | } 66 | System.out.println((double) (System.nanoTime() - l) / 1000000 + "ms elapsed (Getters, raw x100000), got " + 100000 + " values"); 67 | 68 | //testing time taken to instantiate and fill 500k alternating layers of dirt/stone. Unrealistic stress test. 69 | System.out.println(); 70 | System.out.println("Beginning fill for stress-test"); 71 | l = System.nanoTime(); 72 | Palette p2 = new RandomPalette<>(new FastRandom(2403)); 73 | for(int i = 0; i < 1000; i++) { 74 | p2.add(Material.DIRT, 1); 75 | p2.add(Material.STONE, 1); 76 | } 77 | 78 | //testing time taken to retrieve all 1m layers created in previous test. Unrealistic stress test. 79 | System.out.println((System.nanoTime() - l) / 1000000 + "ms elapsed (Instantiation/Fill x500000)"); 80 | l = System.nanoTime(); 81 | for(int i = 0; i < 1000000; i++) { 82 | long l2 = System.nanoTime(); 83 | if(i % 100001 == 0) 84 | System.out.println(p2.get(i, 0, 0) + " retrieved in " + (System.nanoTime() - l2) / 1000 + "us at layer " + i); 85 | } 86 | System.out.println((double) (System.nanoTime() - l) / 1000000 + "ms elapsed (Getters, raw x1000000), got " + 1000000 + " values"); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/test/java/RandomTest.java: -------------------------------------------------------------------------------- 1 | import org.bukkit.Material; 2 | import org.junit.jupiter.api.Test; 3 | import org.polydev.gaea.math.ProbabilityCollection; 4 | import org.polydev.gaea.util.FastRandom; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.function.Consumer; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | 12 | public class RandomTest { 13 | @Test 14 | public void test() { 15 | FastRandom r = new FastRandom(1234); 16 | 17 | FastRandom r2 = new FastRandom(1234); 18 | for(int i = 0; i < 1000; i++) assertEquals(r.nextInt(1234), r2.nextInt(1234)); 19 | } 20 | 21 | @Test 22 | public void prob() { 23 | 24 | 25 | 26 | /* 27 | ProbabilityCollection old = new ProbabilityCollection<>(); 28 | 29 | old.add(Material.DIRT, 500); 30 | old.add(Material.GRASS_BLOCK, 1000); 31 | old.add(Material.ACACIA_FENCE, 1000); 32 | old.add(Material.ACACIA_BOAT, 1000); 33 | old.add(Material.ACACIA_BUTTON, 1000); 34 | old.add(Material.ACACIA_DOOR, 1000); 35 | old.add(Material.ACACIA_FENCE_GATE, 1000); 36 | old.add(Material.ACACIA_LEAVES, 1000); 37 | 38 | FastRandom random = new FastRandom(12); 39 | System.out.println("Old: "); 40 | performance(index -> old.get(random)); 41 | */ 42 | 43 | 44 | ProbabilityCollection fast = new ProbabilityCollection<>(); 45 | 46 | fast.add(Material.DIRT, 500); 47 | fast.add(Material.GRASS_BLOCK, 1000); 48 | fast.add(Material.ACACIA_FENCE, 1000); 49 | fast.add(Material.ACACIA_BOAT, 1000); 50 | fast.add(Material.ACACIA_BUTTON, 1000); 51 | fast.add(Material.ACACIA_DOOR, 1000); 52 | fast.add(Material.ACACIA_FENCE_GATE, 1000); 53 | fast.add(Material.ACACIA_LEAVES, 1000); 54 | 55 | 56 | FastRandom random2 = new FastRandom(12); 57 | System.out.println("New: "); 58 | performance(index -> fast.get(random2)); 59 | 60 | System.out.println(fast.size() + " / " + fast.getTotalProbability()); 61 | 62 | 63 | } 64 | 65 | private void performance(Consumer consumer) { 66 | List times = new ArrayList<>(); 67 | 68 | int num = 100000; 69 | for(int i = 0; i < num; i++) { 70 | long l = System.nanoTime(); 71 | consumer.accept(i); 72 | long r = System.nanoTime() - l; 73 | times.add(r); 74 | } 75 | 76 | long total = times.stream().mapToLong(lon -> lon).sum(); 77 | 78 | double max = ((double) times.stream().mapToLong(lon -> lon).max().getAsLong()); 79 | double min = ((double) times.stream().mapToLong(lon -> lon).min().getAsLong()); 80 | 81 | double avg = ((double) total / times.size()); 82 | 83 | System.out.println(format(min) + " / " + format(avg) + " / " + format(max)); 84 | System.out.println("Total: " + format(total)); 85 | } 86 | 87 | private String format(double ns) { 88 | String[] units = new String[] {"ns", "us", "ms", "s"}; 89 | 90 | int i = 0; 91 | 92 | while(ns > 1000 && i < 4) { 93 | ns /= 1000; 94 | i++; 95 | } 96 | 97 | return ns + units[i]; 98 | } 99 | } 100 | --------------------------------------------------------------------------------